From 8314c8c9e6c7f0b810d2b78f1ecfd5df87bdd752 Mon Sep 17 00:00:00 2001 From: Iustin Pop Date: Sun, 15 May 2016 04:36:11 +0200 Subject: [PATCH] Imported Upstream version 0.5.3 --- Makefile | 1 + NEWS | 11 +++ PKG-INFO | 4 +- README | 6 +- acl.c | 152 +++++++++++++++++++++++++++---------- doc/conf.py | 6 +- pylibacl.egg-info/PKG-INFO | 4 +- setup.py | 6 +- test/test_acls.py | 92 +++++++++++++++++++--- 9 files changed, 220 insertions(+), 62 deletions(-) diff --git a/Makefile b/Makefile index 2005cc3..64e8747 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ test: clean: rm -rf $(DOCHTML) $(DOCTREES) rm -f $(MODNAME) + rm -f *.so rm -rf build .PHONY: doc test clean dist diff --git a/NEWS b/NEWS index 95bdd6d..7f9108f 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,17 @@ News ==== +Version 0.5.3 +------------- + +FreeBSD fixes: + +- Enable all FreeBSD versions after 7.x at level 2 (thanks to Garrett + Cooper). +- Make test suite pass under FreeBSD, which has a stricter behaviour + with regards to invalid ACLs (which we do exercise in the test suite), + thanks again to Garret for the bug reports. + Version 0.5.2 ------------- diff --git a/PKG-INFO b/PKG-INFO index 7cd2842..15fd781 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,10 +1,10 @@ Metadata-Version: 1.0 Name: pylibacl -Version: 0.5.2 +Version: 0.5.3 Summary: POSIX.1e ACLs for python Home-page: http://pylibacl.k1024.org/ Author: Iustin Pop -Author-email: iusty@k1024.org +Author-email: iustin@k1024.org License: LGPL Description: This is a C extension module for Python which implements POSIX ACLs manipulation. It is a wrapper on top diff --git a/README b/README index 6978428..7d1934f 100644 --- a/README +++ b/README @@ -5,8 +5,8 @@ This is a Python 2.4+ extension module allows you to manipulate the POSIX.1e Access Control Lists present in some OS/file-systems combinations. -Downloads: go to https://github.com/iustin/pylibacl/downloads. Latest -version is 0.5.2. The source repository is either +Downloads: go to http://pylibacl.k1024.org/downloads. Latest +version is 0.5.3. The source repository is either at ``_ or at https://github.com/iustin/pylibacl. @@ -33,7 +33,7 @@ Solaris does, but I can't test it. License ------- -pylibacl is Copyright (C) 2002-2009, 2012, 2014 Iustin Pop. +pylibacl is Copyright (C) 2002-2009, 2012, 2014, 2015 Iustin Pop. pylibacl is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free diff --git a/acl.c b/acl.c index 6c0851b..0aba1a7 100644 --- a/acl.c +++ b/acl.c @@ -1,7 +1,7 @@ /* posix1e - a python module exposing the posix acl functions - Copyright (C) 2002-2009, 2012 Iustin Pop + Copyright (C) 2002-2009, 2012, 2014 Iustin Pop This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -672,6 +672,46 @@ static PyObject* ACL_append(PyObject *obj, PyObject *args) { /***** Entry type *****/ +typedef struct { + acl_tag_t tag; + union { + uid_t uid; + gid_t gid; + }; +} tag_qual; + +/* Helper function to get the tag and qualifier of an Entry at the + same time. This is "needed" because the acl_get_qualifier function + returns a pointer to different types, based on the tag value, and + thus it's not straightforward to get the right type. + + It sets a Python exception if an error occurs, and return 0 in this + case. If successful, the tag is set to the tag type, and the + qualifier (if any) to either the uid or the gid entry in the + tag_qual structure. +*/ +int get_tag_qualifier(acl_entry_t entry, tag_qual *tq) { + void *p; + + if(acl_get_tag_type(entry, &tq->tag) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return 0; + } + if (tq->tag == ACL_USER || tq->tag == ACL_GROUP) { + if((p = acl_get_qualifier(entry)) == NULL) { + PyErr_SetFromErrno(PyExc_IOError); + return 0; + } + if (tq->tag == ACL_USER) { + tq->uid = *(uid_t*)p; + } else { + tq->gid = *(gid_t*)p; + } + acl_free(p); + } + return 1; +} + /* Creation of a new Entry instance */ static PyObject* Entry_new(PyTypeObject* type, PyObject* args, PyObject *keywds) { @@ -725,31 +765,18 @@ static void Entry_dealloc(PyObject* obj) { /* Converts the entry to a text format */ static PyObject* Entry_str(PyObject *obj) { - acl_tag_t tag; - uid_t qualifier; - void *p; PyObject *format, *kind; Entry_Object *self = (Entry_Object*) obj; + tag_qual tq; - if(acl_get_tag_type(self->entry, &tag) == -1) { - PyErr_SetFromErrno(PyExc_IOError); + if(!get_tag_qualifier(self->entry, &tq)) { return NULL; } - if(tag == ACL_USER || tag == ACL_GROUP) { - if((p = acl_get_qualifier(self->entry)) == NULL) { - PyErr_SetFromErrno(PyExc_IOError); - return NULL; - } - qualifier = *(uid_t*)p; - acl_free(p); - } else { - qualifier = 0; - } format = MyString_FromString("ACL entry for "); if(format == NULL) return NULL; - switch(tag) { + switch(tq.tag) { case ACL_UNDEFINED_TAG: kind = MyString_FromString("undefined type"); break; @@ -763,10 +790,14 @@ static PyObject* Entry_str(PyObject *obj) { kind = MyString_FromString("the others"); break; case ACL_USER: - kind = MyString_FromFormat("user with uid %d", qualifier); + /* FIXME: here and in the group case, we're formatting with + unsigned, because there's no way to automatically determine + the signed-ness of the types; on Linux(glibc) they're + unsigned, so we'll go along with that */ + kind = MyString_FromFormat("user with uid %u", tq.uid); break; case ACL_GROUP: - kind = MyString_FromFormat("group with gid %d", qualifier); + kind = MyString_FromFormat("group with gid %u", tq.gid); break; case ACL_MASK: kind = MyString_FromString("the mask"); @@ -828,7 +859,11 @@ static PyObject* Entry_get_tag_type(PyObject *obj, void* arg) { */ static int Entry_set_qualifier(PyObject* obj, PyObject* value, void* arg) { Entry_Object *self = (Entry_Object*) obj; - int uidgid; + long uidgid; + uid_t uid; + gid_t gid; + void *p; + acl_tag_t tag; if(value == NULL) { PyErr_SetString(PyExc_TypeError, @@ -838,11 +873,46 @@ static int Entry_set_qualifier(PyObject* obj, PyObject* value, void* arg) { if(!PyInt_Check(value)) { PyErr_SetString(PyExc_TypeError, - "tag type must be integer"); + "qualifier must be integer"); return -1; } - uidgid = PyInt_AsLong(value); - if(acl_set_qualifier(self->entry, (void*)&uidgid) == -1) { + if((uidgid = PyInt_AsLong(value)) == -1) { + if(PyErr_Occurred() != NULL) { + return -1; + } + } + /* Due to how acl_set_qualifier takes its argument, we have to do + this ugly dance with two variables and a pointer that will + point to one of them. */ + if(acl_get_tag_type(self->entry, &tag) == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return -1; + } + uid = uidgid; + gid = uidgid; + switch(tag) { + case ACL_USER: + if((long)uid != uidgid) { + PyErr_SetString(PyExc_OverflowError, "cannot assign given qualifier"); + return -1; + } else { + p = &uid; + } + break; + case ACL_GROUP: + if((long)gid != uidgid) { + PyErr_SetString(PyExc_OverflowError, "cannot assign given qualifier"); + return -1; + } else { + p = &gid; + } + break; + default: + PyErr_SetString(PyExc_TypeError, + "can only set qualifiers on ACL_USER or ACL_GROUP entries"); + return -1; + } + if(acl_set_qualifier(self->entry, p) == -1) { PyErr_SetFromErrno(PyExc_IOError); return -1; } @@ -853,20 +923,26 @@ static int Entry_set_qualifier(PyObject* obj, PyObject* value, void* arg) { /* Returns the qualifier of the entry */ static PyObject* Entry_get_qualifier(PyObject *obj, void* arg) { Entry_Object *self = (Entry_Object*) obj; - void *p; - int value; + long value; + tag_qual tq; if (self->entry == NULL) { PyErr_SetString(PyExc_AttributeError, "entry attribute"); return NULL; } - if((p = acl_get_qualifier(self->entry)) == NULL) { - PyErr_SetFromErrno(PyExc_IOError); + if(!get_tag_qualifier(self->entry, &tq)) { + return NULL; + } + if (tq.tag == ACL_USER) { + value = tq.uid; + } else if (tq.tag == ACL_GROUP) { + value = tq.gid; + } else { + PyErr_SetString(PyExc_TypeError, + "given entry doesn't have an user or" + " group tag"); return NULL; } - value = *(uid_t*)p; - acl_free(p); - return PyInt_FromLong(value); } @@ -1767,17 +1843,15 @@ void initposix1e(void) PyModule_AddIntConstant(m, "ACL_MISS_ERROR", ACL_MISS_ERROR); PyModule_AddIntConstant(m, "ACL_ENTRY_ERROR", ACL_ENTRY_ERROR); - /* declare the Linux extensions */ - PyModule_AddIntConstant(m, "HAS_ACL_FROM_MODE", 1); - PyModule_AddIntConstant(m, "HAS_ACL_CHECK", 1); - PyModule_AddIntConstant(m, "HAS_EXTENDED_CHECK", 1); - PyModule_AddIntConstant(m, "HAS_EQUIV_MODE", 1); +#define LINUX_EXT_VAL 1 #else - PyModule_AddIntConstant(m, "HAS_ACL_FROM_MODE", 0); - PyModule_AddIntConstant(m, "HAS_ACL_CHECK", 0); - PyModule_AddIntConstant(m, "HAS_EXTENDED_CHECK", 0); - PyModule_AddIntConstant(m, "HAS_EQUIV_MODE", 0); +#define LINUX_EXT_VAL 0 #endif + /* declare the Linux extensions */ + PyModule_AddIntConstant(m, "HAS_ACL_FROM_MODE", LINUX_EXT_VAL); + PyModule_AddIntConstant(m, "HAS_ACL_CHECK", LINUX_EXT_VAL); + PyModule_AddIntConstant(m, "HAS_EXTENDED_CHECK", LINUX_EXT_VAL); + PyModule_AddIntConstant(m, "HAS_EQUIV_MODE", LINUX_EXT_VAL); #ifdef IS_PY3K return m; diff --git a/doc/conf.py b/doc/conf.py index e8299c1..5861a43 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -41,16 +41,16 @@ master_doc = 'index' # General information about the project. project = u'pylibacl' -copyright = u'2002-2009, 2012, 2014, Iustin Pop' +copyright = u'2002-2009, 2012, 2014, 2015, Iustin Pop' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.5.2' +version = '0.5.3' # The full version, including alpha/beta/rc tags. -release = '0.5.2' +release = '0.5.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pylibacl.egg-info/PKG-INFO b/pylibacl.egg-info/PKG-INFO index 7cd2842..15fd781 100644 --- a/pylibacl.egg-info/PKG-INFO +++ b/pylibacl.egg-info/PKG-INFO @@ -1,10 +1,10 @@ Metadata-Version: 1.0 Name: pylibacl -Version: 0.5.2 +Version: 0.5.3 Summary: POSIX.1e ACLs for python Home-page: http://pylibacl.k1024.org/ Author: Iustin Pop -Author-email: iusty@k1024.org +Author-email: iustin@k1024.org License: LGPL Description: This is a C extension module for Python which implements POSIX ACLs manipulation. It is a wrapper on top diff --git a/setup.py b/setup.py index 9c8f2cd..aaaee63 100755 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ elif u_sysname == "GNU/kFreeBSD": libs.append("acl") elif u_sysname == "FreeBSD": macros.append(("HAVE_FREEBSD", None)) - if u_release.startswith("7."): + if int(u_release.split(".", 1)[0]) >= 7: macros.append(("HAVE_LEVEL2", None)) elif u_sysname == "Darwin": libs.append("pthread") @@ -30,14 +30,14 @@ long_desc = """This is a C extension module for Python which implements POSIX ACLs manipulation. It is a wrapper on top of the systems's acl C library - see acl(5).""" -version = "0.5.2" +version = "0.5.3" setup(name="pylibacl", version=version, description="POSIX.1e ACLs for python", long_description=long_desc, author="Iustin Pop", - author_email="iusty@k1024.org", + author_email="iustin@k1024.org", url="http://pylibacl.k1024.org/", license="LGPL", ext_modules=[Extension("posix1e", ["acl.c"], diff --git a/test/test_acls.py b/test/test_acls.py index 08bfd4e..8eb3e4d 100644 --- a/test/test_acls.py +++ b/test/test_acls.py @@ -3,7 +3,7 @@ """Unittests for the posix1e module""" -# Copyright (C) 2002-2009, 2012 Iustin Pop +# Copyright (C) 2002-2009, 2012, 2014, 2015 Iustin Pop # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -26,6 +26,8 @@ import os import tempfile import sys import platform +import re +import errno import posix1e from posix1e import * @@ -40,6 +42,9 @@ M0500 = 320 # octal 0500 M0644 = 420 # octal 0644 M0755 = 493 # octal 755 +# Check if running under Python 3 +IS_PY_3K = sys.hexversion >= 0x03000000 + def _skip_test(fn): """Wrapper to skip a test""" new_fn = lambda x: None @@ -54,6 +59,20 @@ def has_ext(extension): else: return lambda x: x +def ignore_ioerror(errnum, fn, *args, **kwargs): + """Call a function while ignoring some IOErrors. + + This is needed as some OSes (e.g. FreeBSD) return failure (EINVAL) + when doing certain operations on an invalid ACL. + + """ + try: + fn(*args, **kwargs) + except IOError: + err = sys.exc_info()[1] + if err.errno == errnum: + return + raise class aclTest: """Support functions ACLs""" @@ -210,7 +229,7 @@ class ModificationTests(aclTest, unittest.TestCase): def testStr(self): """Test str() of an ACL.""" - acl = posix1e.ACL() + acl = posix1e.ACL(text=BASIC_ACL_TEXT) str_acl = str(acl) self.checkRef(str_acl) @@ -220,7 +239,7 @@ class ModificationTests(aclTest, unittest.TestCase): acl = posix1e.ACL() e = acl.append() e.tag_type = posix1e.ACL_OTHER - acl.calc_mask() + ignore_ioerror(errno.EINVAL, acl.calc_mask) str_format = str(e) self.checkRef(str_format) @@ -230,9 +249,9 @@ class ModificationTests(aclTest, unittest.TestCase): acl = posix1e.ACL() e = acl.append() e.tag_type = posix1e.ACL_OTHER - acl.calc_mask() + ignore_ioerror(errno.EINVAL, acl.calc_mask) acl.delete_entry(e) - acl.calc_mask() + ignore_ioerror(errno.EINVAL, acl.calc_mask) @has_ext(HAS_ACL_ENTRY) def testDoubleEntries(self): @@ -269,11 +288,11 @@ class ModificationTests(aclTest, unittest.TestCase): @has_ext(HAS_ACL_ENTRY) def testMultipleBadEntries(self): """Test multiple invalid entries""" - acl = posix1e.ACL(text=BASIC_ACL_TEXT) - self.assertTrue(acl.valid(), "ACL built from standard description" - " should be valid") for tag_type in (posix1e.ACL_USER, posix1e.ACL_GROUP): + acl = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertTrue(acl.valid(), "ACL built from standard description" + " should be valid") e1 = acl.append() e1.tag_type = tag_type e1.qualifier = 0 @@ -285,11 +304,13 @@ class ModificationTests(aclTest, unittest.TestCase): e2.tag_type = tag_type e2.qualifier = 0 e2.permset.clear() - acl.calc_mask() + ignore_ioerror(errno.EINVAL, acl.calc_mask) self.assertFalse(acl.valid(), "ACL should not validate when" " containing two duplicate entries") acl.delete_entry(e1) - acl.delete_entry(e2) + # FreeBSD trips over itself here and can't delete the + # entry, even though it still exists. + ignore_ioerror(errno.EINVAL, acl.delete_entry, e2) @has_ext(HAS_ACL_ENTRY) def testPermset(self): @@ -320,5 +341,56 @@ class ModificationTests(aclTest, unittest.TestCase): " after deletion" % pmap[perm]) + @has_ext(HAS_ACL_ENTRY and IS_PY_3K) + def testQualifierValues(self): + """Tests qualifier correct store/retrieval""" + acl = posix1e.ACL() + e = acl.append() + # work around deprecation warnings + if hasattr(self, 'assertRegex'): + fn = self.assertRegex + else: + fn = self.assertRegexpMatches + for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]: + qualifier = 1 + e.tag_type = tag + while True: + if tag == posix1e.ACL_USER: + regex = re.compile("user with uid %d" % qualifier) + else: + regex = re.compile("group with gid %d" % qualifier) + try: + e.qualifier = qualifier + except OverflowError: + # reached overflow condition, break + break + self.assertEqual(e.qualifier, qualifier) + fn(str(e), regex) + qualifier *= 2 + + @has_ext(HAS_ACL_ENTRY and IS_PY_3K) + def testQualifierOverflow(self): + """Tests qualifier overflow handling""" + acl = posix1e.ACL() + e = acl.append() + qualifier = sys.maxsize * 2 + for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]: + e.tag_type = tag + with self.assertRaises(OverflowError): + e.qualifier = qualifier + + @has_ext(HAS_ACL_ENTRY and IS_PY_3K) + def testNegativeQualifier(self): + """Tests negative qualifier handling""" + # Note: this presumes that uid_t/gid_t in C are unsigned... + acl = posix1e.ACL() + e = acl.append() + for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]: + e.tag_type = tag + for qualifier in [-10, -5, -1]: + with self.assertRaises(OverflowError): + e.qualifier = qualifier + + if __name__ == "__main__": unittest.main() -- 2.39.2