From 8314c8c9e6c7f0b810d2b78f1ecfd5df87bdd752 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@k1024.org>
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 `<git://git.k1024.org/pylibacl.git>`_ 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 <iusty@k1024.org>
+    Copyright (C) 2002-2009, 2012, 2014 Iustin Pop <iustin@k1024.org>
 
     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 <iusty@k1024.org>
+#  Copyright (C) 2002-2009, 2012, 2014, 2015 Iustin Pop <iustin@k1024.org>
 #
 #  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.5