From dee660f2c4164dbe8862e0158494dbfe966b8c43 Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Tue, 5 May 2009 02:52:19 -0400 Subject: [PATCH] ACL implementation. Current features: * afs.acl.ACL.retrieve(), equivalent to fs listacl * afs.acl.getCallerAccess(), equivalent to fs getcalleraccess Permissions are bitmasked, which can be masked against afs.acl.READ, afs.acl.WRITE, etc. Implementation is based off of Perl AFS module, although there are some notable differences in API. Signed-off-by: Edward Z. Yang --- afs/_acl.pyx | 46 ++++++++++++++++++++++++++++ afs/acl.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ afs/afs.pxd | 17 +++++++++++ afs/afs.pyx | 18 +++++++++++ afs/tests/test_acl.py | 18 +++++++++++ setup.py | 1 + 6 files changed, 179 insertions(+) create mode 100644 afs/_acl.pyx create mode 100644 afs/acl.py create mode 100644 afs/tests/test_acl.py diff --git a/afs/_acl.pyx b/afs/_acl.pyx new file mode 100644 index 0000000..5f0360b --- /dev/null +++ b/afs/_acl.pyx @@ -0,0 +1,46 @@ +from afs cimport * +from afs import pyafs_error + +cdef import from "afs/prs_fs.h": + enum: + PRSFS_READ, PRSFS_WRITE, PRSFS_INSERT, PRSFS_LOOKUP, + PRSFS_DELETE, PRSFS_LOCK, PRSFS_ADMINISTER, + PRSFS_USR0, PRSFS_USR1, PRSFS_USR2, PRSFS_USR2, PRSFS_USR3, + PRSFS_USR4, PRSFS_USR5, PRSFS_USR6, PRSFS_USR7 + +# This is defined in afs/afs.h, but I can't figure how to include the +# header. Also, venus/fs.c redefines the struct, so why not! +cdef struct vcxstat2: + afs_int32 callerAccess + afs_int32 cbExpires + afs_int32 anyAccess + char mvstat + +READ = PRSFS_READ +WRITE = PRSFS_WRITE +INSERT = PRSFS_INSERT +LOOKUP = PRSFS_LOOKUP +DELETE = PRSFS_DELETE +LOCK = PRSFS_LOCK +ADMINISTER = PRSFS_ADMINISTER +USR0 = PRSFS_USR0 +USR1 = PRSFS_USR1 +USR2 = PRSFS_USR2 +USR3 = PRSFS_USR3 +USR4 = PRSFS_USR4 +USR5 = PRSFS_USR5 +USR6 = PRSFS_USR6 +USR7 = PRSFS_USR7 + +DEF MAXSIZE = 2048 + +def getAcl(char* dir, int follow=1): + cdef char space[MAXSIZE] + pioctl_read(dir, VIOCGETAL, space, MAXSIZE, follow) + ret = space # Python managed string + return ret + +def getCallerAccess(char *dir, int follow=1): + cdef vcxstat2 stat + pioctl_read(dir, VIOC_GETVCXSTATUS2, &stat, sizeof(vcxstat2), follow) + return stat.callerAccess diff --git a/afs/acl.py b/afs/acl.py new file mode 100644 index 0000000..b5da255 --- /dev/null +++ b/afs/acl.py @@ -0,0 +1,79 @@ +import _acl +from _acl import READ, WRITE, INSERT, LOOKUP, DELETE, LOCK, ADMINISTER, \ + USR0, USR1, USR2, USR3, USR4, USR5, USR6, USR7 +from _acl import getCallerAccess + +_canonical = { + "read": "rl", + "write": "rwlidwk", + "all": "rwlidwka", + "mail": "lik", + "none": "", +} +_char2bit = { + 'r': READ, + 'w': WRITE, + 'i': INSERT, + 'l': LOOKUP, + 'd': DELETE, + 'k': LOCK, + 'a': ADMINISTER, + 'A': USR0, + 'B': USR1, + 'C': USR2, + 'D': USR3, + 'E': USR4, + 'F': USR5, + 'G': USR6, + 'H': USR7, +} + +_bit2char = dict([(v,k) for k,v in _char2bit.items()]) + +def crights(s): + """Canonicalizes string rights to bitmask""" + if s in _canonical: s = _canonical[s] + return _parseRights(s) + +class ACL(object): + def __init__(self, pos, neg): + """ + ``pos`` + Dictionary of usernames to positive ACL bitmasks + ``neg`` + Dictionary of usernames to negative ACL bitmasks + """ + self.pos = pos + self.neg = neg + @staticmethod + def retrieve(dir): + """Retrieve the ACL for an AFS directory""" + pos, neg = _parseAcl(_acl.getAcl(dir)) + return ACL(pos, neg) + +def _parseRights(s): + """Parses a rwlid... rights tring to bitmask""" + r = 0 + try: + for c in s: + r = r | _char2bit[c] + except KeyError: + raise ValueError + return r + +def _parseAcl(inp): + lines = inp.split("\n") + npos = int(lines[0].split(" ")[0]) + pos = {} + neg = {} + for l in lines[2:]: + if l == "": continue + name, acl = l.split() + if npos: + npos -= 1 + pos[name] = int(acl) + else: + # negative acl + neg[name] = int(acl) + return (pos, neg) + diff --git a/afs/afs.pxd b/afs/afs.pxd index d165921..02518e2 100644 --- a/afs/afs.pxd +++ b/afs/afs.pxd @@ -146,3 +146,20 @@ cdef extern from "rx/rxkad.h": void initialize_RXK_error_table() cdef extern from "ubik.h": void initialize_U_error_table() + +cdef extern from "afs/vice.h": + struct ViceIoctl: + void *cin "in" + void *out + unsigned short out_size + unsigned short in_size + +cdef import from "afs/venus.h": + enum: + # PIOCTLS to Venus that we use + VIOCGETAL, VIOC_GETVCXSTATUS2 + +# pioctl doesn't actually have a header, so we have to define it here +cdef extern int pioctl(char *, afs_int32, ViceIoctl *, afs_int32) +cdef int pioctl_read(char *, afs_int32, void *, unsigned short, afs_int32) except -1 + diff --git a/afs/afs.pyx b/afs/afs.pyx index 8f51c16..77456b4 100644 --- a/afs/afs.pyx +++ b/afs/afs.pyx @@ -4,8 +4,26 @@ General PyAFS utilities, such as error handling import sys +# otherwise certain headers are unhappy +cdef import from "netinet/in.h": pass +cdef import from "afs/vice.h": pass + cdef int _init = 0 +# pioctl convenience wrappers + +cdef extern int pioctl_read(char *dir, afs_int32 op, void *buffer, unsigned short size, afs_int32 follow) except -1: + cdef ViceIoctl blob + cdef afs_int32 code + blob.in_size = 0 + blob.out_size = size + blob.out = buffer + code = pioctl(dir, op, &blob, follow) + pyafs_error(code) + return code + +# Error handling + class AFSException(Exception): def __init__(self, errno): self.errno = errno diff --git a/afs/tests/test_acl.py b/afs/tests/test_acl.py new file mode 100644 index 0000000..385e2ad --- /dev/null +++ b/afs/tests/test_acl.py @@ -0,0 +1,18 @@ +import nose +import afs.acl as acl + +def test_crights(): + assert acl.crights('read') & acl.READ + assert acl.crights('read') & acl.LOOKUP + assert not acl.crights('read') & acl.WRITE + +def test_retrieve(): + assert acl.ACL.retrieve('/afs/athena.mit.edu/contrib/bitbucket2').pos['system:anyuser'] & acl.WRITE + assert acl.ACL.retrieve('/afs/athena.mit.edu/user/t/a/tabbott').neg['yuranlu'] & acl.USR0 + +def test_getCallerAccess(): + assert acl.getCallerAccess('/afs/athena.mit.edu/contrib/bitbucket2') & acl.WRITE + +if __name__ == '__main__': + nose.main() + diff --git a/setup.py b/setup.py index e3a5133..912231c 100755 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ setup( ext_modules=[ PyAFSExtension("afs.afs"), PyAFSExtension("afs._pts"), + PyAFSExtension("afs._acl"), ], cmdclass= {"build_ext": build_ext} ) -- 1.7.9.5