From 137d9e9aa87890dbd343383f636652374dbd72db Mon Sep 17 00:00:00 2001 From: Jonathan Reed Date: Fri, 13 Nov 2009 16:57:40 -0500 Subject: [PATCH 01/16] Implement the ability to set ACLs. - Added pioctl_write and setAcl to the Pyrex code - Added ACL.set to set an ACL to a given bitmask, ACL.apply to actually apply the ACL. ACLs set to 0 are pruned before applying. Signed-off-by: Jonathan Reed --- afs/_acl.pyx | 3 +++ afs/acl.py | 35 ++++++++++++++++++++++++++++++++++- afs/afs.pxd | 4 +++- afs/afs.pyx | 14 ++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/afs/_acl.pyx b/afs/_acl.pyx index ef65388..3f34685 100644 --- a/afs/_acl.pyx +++ b/afs/_acl.pyx @@ -43,3 +43,6 @@ def getCallerAccess(char *dir, int follow=1): cdef vcxstat2 stat pioctl_read(dir, VIOC_GETVCXSTATUS2, &stat, sizeof(vcxstat2), follow) return stat.callerAccess + +def setAcl(char* dir, char* acl, int follow=1): + pioctl_write(dir, VIOCSETAL, acl, follow) diff --git a/afs/acl.py b/afs/acl.py index 713d5b8..1fb1dac 100644 --- a/afs/acl.py +++ b/afs/acl.py @@ -70,6 +70,16 @@ def _parseAcl(inp): neg[name] = int(acl) return (pos, neg) +def _unparseAcl(pos, neg): + npos = len(pos) + nneg = len(neg) + acl = "%d\n%d\n" % (npos, nneg) + for p in pos.items(): + acl += "%s\t%d\n" % p + for n in neg.items(): + acl += "%s\t%d\n" % n + return acl + class ACL(object): def __init__(self, pos, neg): """ @@ -85,4 +95,27 @@ class ACL(object): """Retrieve the ACL for an AFS directory""" pos, neg = _parseAcl(_acl.getAcl(dir, follow)) return ACL(pos, neg) - + def apply(self, dir, follow=True): + """Apply the ACL to a directory""" + self._clean() + _acl.setAcl(dir, _unparseAcl(self.pos, self.neg), follow) + def _clean(self): + """Clean an ACL by removing any entries whose bitmask is 0""" + for n,a in self.pos.items(): + if a == 0: + del self.pos[n] + for n,a in self.neg.items(): + if a == 0: + del self.neg[n] + def set(self, user, bitmask, negative=False): + """Set the bitmask for a given user""" + if bitmask < 0 or bitmask > max(_char2bit.values()): + raise ValueError, "Invalid bitmask" + if negative: + self.neg[user] = bitmask + else: + self.pos[user] = bitmask + def remove(self, user, negative=False): + """Convenience function to removeSet the bitmask for a given user""" + self.set(user, 0, negative) + diff --git a/afs/afs.pxd b/afs/afs.pxd index 02518e2..2b02338 100644 --- a/afs/afs.pxd +++ b/afs/afs.pxd @@ -9,6 +9,7 @@ cdef extern from "string.h": char * strncpy(char *s1, char *s2, size_t n) void * memset(void *b, int c, size_t n) void * memcpy(void *s1, void *s2, size_t n) + size_t strlen(char *s) cdef extern from "stdlib.h": void * malloc(size_t size) @@ -157,9 +158,10 @@ cdef extern from "afs/vice.h": cdef import from "afs/venus.h": enum: # PIOCTLS to Venus that we use - VIOCGETAL, VIOC_GETVCXSTATUS2 + VIOCGETAL, VIOC_GETVCXSTATUS2, VIOCSETAL # 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 +cdef int pioctl_write(char *, afs_int32, char *, afs_int32) except -1 diff --git a/afs/afs.pyx b/afs/afs.pyx index 29a2442..4fe9e1c 100644 --- a/afs/afs.pyx +++ b/afs/afs.pyx @@ -26,6 +26,20 @@ cdef extern int pioctl_read(char *dir, afs_int32 op, void *buffer, unsigned shor pyafs_error(code) return code +cdef extern int pioctl_write(char *dir, afs_int32 op, char *buffer, afs_int32 follow) except -1: + cdef ViceIoctl blob + cdef afs_int32 code + blob.cin = buffer + blob.in_size = 1 + strlen(buffer) + blob.out_size = 0 + code = pioctl(dir, op, &blob, follow) + # This might work with the rest of OpenAFS, but I'm not convinced + # the rest of it is consistent + if code == -1: + raise OSError(errno, strerror(errno)) + pyafs_error(code) + return code + # Error handling class AFSException(Exception): -- 1.7.9.5 From f527042f132d70a36955170652a81182d4884c11 Mon Sep 17 00:00:00 2001 From: Jonathan Reed Date: Fri, 13 Nov 2009 17:03:56 -0500 Subject: [PATCH 02/16] Remove extra 'w' from canonical strings for 'write' and 'all Signed-off-by: Jonathan Reed --- afs/acl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/afs/acl.py b/afs/acl.py index 1fb1dac..c49f374 100644 --- a/afs/acl.py +++ b/afs/acl.py @@ -5,8 +5,8 @@ from _acl import getCallerAccess _canonical = { "read": "rl", - "write": "rwlidwk", - "all": "rwlidwka", + "write": "rlidwk", + "all": "rlidwka", "mail": "lik", "none": "", } -- 1.7.9.5 From 09b4d45674f9d37d44c68f52c9a965b14b7e336f Mon Sep 17 00:00:00 2001 From: Jonathan Reed Date: Fri, 13 Nov 2009 17:04:41 -0500 Subject: [PATCH 03/16] Change order of bits in list of tuples to return ACLS in same format as AFS (rlidwka vs rwildka). Add ability to turn a rights string into a canonical name Signed-off-by: Jonathan Reed --- afs/acl.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/afs/acl.py b/afs/acl.py index c49f374..a5293bd 100644 --- a/afs/acl.py +++ b/afs/acl.py @@ -11,12 +11,14 @@ _canonical = { "none": "", } +_reverseCanonical = dict((y, x) for (x, y) in _canonical.iteritems()) + _charBitAssoc = [ ('r', READ), - ('w', WRITE), - ('i', INSERT), ('l', LOOKUP), + ('i', INSERT), ('d', DELETE), + ('w', WRITE), ('k', LOCK), ('a', ADMINISTER), ('A', USR0), @@ -32,6 +34,13 @@ _charBitAssoc = [ _char2bit = dict(_charBitAssoc) +def rightsToEnglish(s): + """Turns a rlwidwka string into a canonical name if possible""" + if s in _reverseCanonical: + return _reverseCanonical[s] + else: + return '' + def readRights(s): """Canonicalizes string rights to bitmask""" if s in _canonical: s = _canonical[s] -- 1.7.9.5 From b96524d6b92de2d8aeb8b1aa5f83f2dbc822f5f2 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Tue, 20 Oct 2009 17:34:49 -0400 Subject: [PATCH 04/16] Figure out what realm an afs._pts.PTS instance authenticates against. Store the Kerberos realm as an attribute for later retrieval. Signed-off-by: Evan Broder --- afs/_pts.pyx | 35 +++++++++++++++++++++++++++++++++++ setup.py | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/afs/_pts.pyx b/afs/_pts.pyx index bef7139..107bf85 100644 --- a/afs/_pts.pyx +++ b/afs/_pts.pyx @@ -71,6 +71,27 @@ cdef import from "afs/pterror.h": enum: PRNOENT +cdef import from "krb5/krb5.h": + struct _krb5_context: + pass + struct krb5_principal_data: + pass + + ctypedef _krb5_context krb5_context + ctypedef krb5_principal_data * krb5_principal + + ctypedef long krb5_int32 + ctypedef krb5_int32 krb5_error_code + krb5_error_code krb5_init_context(krb5_context *) + krb5_error_code krb5_parse_name(krb5_context, char *, krb5_principal *) + krb5_error_code krb5_unparse_name(krb5_context, krb5_principal, char **) + krb5_error_code krb5_524_conv_principal(krb5_context, krb5_principal, char *, char *, char *) + krb5_error_code krb5_425_conv_principal(krb5_context, char *, char *, char *, krb5_principal *) + krb5_error_code krb5_get_host_realm(krb5_context, char *, char ***) + void krb5_free_host_realm(krb5_context, char **) + void krb5_free_principal(krb5_context, krb5_principal) + void krb5_free_context(krb5_context) + cdef class PTEntry: cdef public afs_int32 flags cdef public afs_int32 id @@ -131,14 +152,20 @@ cdef class PTS: - 2: fail if an authenticated connection can't be established - 3: same as 2, plus encrypt all traffic to the protection server + + The realm attribute is the Kerberos realm against which this cell + authenticates. """ cdef ubik_client * client cdef readonly object cell + cdef readonly object realm def __cinit__(self, cell=None, sec=1): cdef afs_int32 code cdef afsconf_dir *cdir cdef afsconf_cell info + cdef krb5_context context + cdef char ** hrealms = NULL cdef char * c_cell cdef ktc_principal prin cdef ktc_token token @@ -167,6 +194,14 @@ cdef class PTS: code = afsconf_GetCellInfo(cdir, c_cell, "afsprot", &info) pyafs_error(code) + code = krb5_init_context(&context) + pyafs_error(code) + code = krb5_get_host_realm(context, info.hostName[0], &hrealms) + pyafs_error(code) + self.realm = hrealms[0] + krb5_free_host_realm(context, hrealms) + krb5_free_context(context) + self.cell = info.name if sec > 0: diff --git a/setup.py b/setup.py index 0a59340..3e593e3 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ setup( packages=['afs', 'afs.tests'], ext_modules=[ PyAFSExtension("afs.afs"), - PyAFSExtension("afs._pts"), + PyAFSExtension("afs._pts", libraries=['krb5']), PyAFSExtension("afs._acl"), ], cmdclass= {"build_ext": build_ext} -- 1.7.9.5 From 4af97584e2401ec114fbbd846a46700613d50a15 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Tue, 20 Oct 2009 17:39:49 -0400 Subject: [PATCH 05/16] Add implementations of kname_parse and kname_unparse to afs._pts. Signed-off-by: Evan Broder --- afs/_pts.pyx | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/afs/_pts.pyx b/afs/_pts.pyx index 107bf85..2ebc064 100644 --- a/afs/_pts.pyx +++ b/afs/_pts.pyx @@ -1,5 +1,6 @@ from afs cimport * from afs import pyafs_error +import re cdef import from "afs/ptuser.h": enum: @@ -77,7 +78,7 @@ cdef import from "krb5/krb5.h": struct krb5_principal_data: pass - ctypedef _krb5_context krb5_context + ctypedef _krb5_context * krb5_context ctypedef krb5_principal_data * krb5_principal ctypedef long krb5_int32 @@ -138,6 +139,28 @@ cdef int _ptentry_to_c(prcheckentry * c_entry, PTEntry p_entry) except -1: strncpy(c_entry.name, p_entry.name, sizeof(c_entry.name)) return 0 +cdef object kname_re = re.compile(r'^([^.].*?)(? Date: Tue, 20 Oct 2009 18:28:05 -0400 Subject: [PATCH 06/16] Add an _AfsToKrb5 method on afs._pts.PTS for converting AFS-style principals to the equivalent Kerberos 5 principals. Signed-off-by: Evan Broder --- afs/_pts.pyx | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/afs/_pts.pyx b/afs/_pts.pyx index 2ebc064..1a90ea6 100644 --- a/afs/_pts.pyx +++ b/afs/_pts.pyx @@ -607,3 +607,50 @@ cdef class PTS: code = ubik_PR_SetFieldsEntry(self.client, 0, id, mask, flags, ngroups, nusers, 0, 0) pyafs_error(code) + + def _AfsToKrb5(self, afs_name): + """Convert an AFS principal to a Kerberos v5 one.""" + cdef krb5_context ctx = NULL + cdef krb5_principal princ = NULL + cdef krb5_error_code code = 0 + cdef char * krb5_princ = NULL + cdef char *name = NULL, *inst = NULL, *realm = NULL + cdef object pname, pinst, prealm + + if '@' in afs_name: + pname, prealm = afs_name.rsplit('@', 1) + prealm = prealm.upper() + krb4_name = '%s@%s' % (pname, prealm) + else: + krb4_name = '%s@%s' % (afs_name, self.realm) + + pname, pinst, prealm = kname_parse(krb4_name) + if pname: + name = pname + if pinst: + inst = pinst + if prealm: + realm = prealm + + code = krb5_init_context(&ctx) + try: + pyafs_error(code) + + code = krb5_425_conv_principal(ctx, name, inst, realm, &princ) + try: + pyafs_error(code) + + code = krb5_unparse_name(ctx, princ, &krb5_princ) + try: + pyafs_error(code) + + return krb5_princ + finally: + if krb5_princ is not NULL: + free(krb5_princ) + finally: + if princ is not NULL: + krb5_free_principal(ctx, princ) + finally: + if ctx is not NULL: + krb5_free_context(ctx) -- 1.7.9.5 From fa8b281a9e135ed7b0380d783ec2b1e81594508e Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Tue, 20 Oct 2009 18:43:12 -0400 Subject: [PATCH 07/16] Add a new _Krb5ToAfs method to afs._pts.PTS for converting Kerberos 5 principals to AFS principals. Signed-off-by: Evan Broder --- afs/_pts.pyx | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/afs/_pts.pyx b/afs/_pts.pyx index 1a90ea6..c347037 100644 --- a/afs/_pts.pyx +++ b/afs/_pts.pyx @@ -654,3 +654,43 @@ cdef class PTS: finally: if ctx is not NULL: krb5_free_context(ctx) + + def _Krb5ToAfs(self, krb5_name): + """Convert a Kerberos v5 principal to an AFS one.""" + cdef krb5_context ctx = NULL + cdef krb5_principal k5_princ = NULL + cdef char *k4_name, *k4_inst, *k4_realm + cdef object afs_princ + cdef object afs_name, afs_realm + + k4_name = malloc(40) + k4_name[0] = '\0' + k4_inst = malloc(40) + k4_inst[0] = '\0' + k4_realm = malloc(40) + k4_realm[0] = '\0' + + code = krb5_init_context(&ctx) + try: + pyafs_error(code) + + code = krb5_parse_name(ctx, krb5_name, &k5_princ) + try: + pyafs_error(code) + + code = krb5_524_conv_principal(ctx, k5_princ, k4_name, k4_inst, k4_realm) + pyafs_error(code) + + afs_princ = kname_unparse(k4_name, k4_inst, k4_realm) + afs_name, afs_realm = afs_princ.rsplit('@', 1) + + if k4_realm == self.realm: + return afs_name + else: + return '%s@%s' % (afs_name, afs_realm.lower()) + finally: + if k5_princ is not NULL: + krb5_free_principal(ctx, k5_princ) + finally: + if ctx is not NULL: + krb5_free_context(ctx) -- 1.7.9.5 From 264e62aff58ab2cdc835cbce60cacca5b4d6236f Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Mon, 27 Jul 2009 01:08:59 -0700 Subject: [PATCH 08/16] First attempt at an ORM-like interface to the PRDB. Missing features that should eventually be added: - Create/delete users/groups - Expose afs._pts.PTS._AfsToKrb5 and afs._pts.PTS._Krb5ToAfs. _Krb5ToAfs could be exposed as a method to return a PTEntry from a Kerberos principal, while _AfsToKrb5 could be a method (or property) of a PTEntry that returns the equivalent Kerberos principal. Signed-off-by: Evan Broder --- afs/pts.py | 377 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 afs/pts.py diff --git a/afs/pts.py b/afs/pts.py new file mode 100644 index 0000000..cfbfb7d --- /dev/null +++ b/afs/pts.py @@ -0,0 +1,377 @@ +import collections +import _pts + +class PTRelationSet(collections.MutableSet): + """Collection class for the groups/members of a PTEntry. + + This class, which acts like a set, is actually a view of the + groups or members associated with a PTS Entry. Changes to this + class are immediately reflected to the PRDB. + + Attributes: + _ent: The PTEntry whose groups/members this instance + represents + _set: If defined, the set of either groups or members for this + instance's PTEntry + """ + def __init__(self, ent): + """Initialize a PTRelationSet class. + + Args: + ent: The PTEntry this instance should be associated with. + """ + super(PTRelationSet, self).__init__() + + self._ent = ent + + def _loadSet(self): + """Load the membership/groups for this instance's PTEntry. + + If they have not previously been loaded, this method updates + self._set with the set of PTEntries that are either members of + this group, or the groups that this entry is a member of. + """ + if not hasattr(self, '_set'): + self._set = set(self._ent._pts.getEntry(m) for m in + self._ent._pts._ListMembers(self._ent.id)) + + def _add(self, elt): + """Add a new PTEntry to this instance's internal representation. + + This method adds a new entry to this instance's set of + members/groups, but unlike PTRelationSet.add, it doesn't add + itself to the other instance's set. + + Args: + elt: The element to add. + """ + self._set.add(self._ent._pts.getEntry(elt)) + + def _discard(self, elt): + """Remove a PTEntry to this instance's internal representation. + + This method removes an entry from this instance's set of + members/groups, but unlike PTRelationSet.discard, it doesn't + remove itself from the other instance's set. + + Args: + elt: The element to discard. + """ + self._set.discard(self._ent._pts.getEntry(elt)) + + def __len__(self): + """Count the members/groups in this set. + + Returns: + The number of entities in this instance. + """ + self._loadSet() + return len(self._set) + + def __iter__(self): + """Iterate over members/groups in this set + + Returns: + An iterator that loops over the members/groups of this + set. + """ + self._loadSet() + return iter(self._set) + + def __contains__(self, name): + """Test if a PTEntry is connected to this instance. + + If the membership of the group hasn't already been loaded, + this method takes advantage of the IsAMemberOf lookup to test + for membership. + + This has the convenient advantage of working even when the + user doens't have permission to enumerate the group's + membership. + + Args: + name: The element whose membership is being tested. + + Returns: + True, if name is a member of self (or if self is a member + of name); otherwise, False + """ + name = self._ent._pts.getEntry(name) + if hasattr(self, '_set'): + return name in self._set + else: + if self._ent.id < 0: + return self._ent._pts._IsAMemberOf(name.id, self._ent.id) + else: + return self._ent._pts._IsAMemberOf(self._ent.id, name.id) + + def __repr__(self): + self._loadSet() + return repr(self._set) + + def add(self, elt): + """Add one new entity to a group. + + This method will add a new user to a group, regardless of + whether this instance represents a group or a user. The change + is also immediately reflected to the PRDB. + + Raises: + TypeError: If you try to add a grop group to a group, or a + user to a user + """ + elt = self._ent._pts.getEntry(elt) + if elt in self: + return + + if self._ent.id < 0: + if elt.id < 0: + raise TypeError( + "Adding group '%s' to group '%s' is not supported." % + (elt, self._ent)) + + self._ent._pts._AddToGroup(elt.id, self._ent.id) + + elt.groups._add(self._ent) + else: + if elt.id > 0: + raise TypeError( + "Can't add user '%s' to user '%s'." % + (elt, self._ent)) + + self._ent._pts._AddToGroup(self._ent.id, elt.id) + + elt.members._add(self._ent) + + self._add(elt) + + def discard(self, elt): + """Remove one entity from a group. + + This method will remove a user from a group, regardless of + whether this instance represents a group or a user. The change + is also immediately reflected to the PRDB. + """ + elt = self._ent._pts.getEntry(elt) + if elt not in self: + return + + if self._ent.id < 0: + self._ent._pts._RemoveFromGroup(elt.id, self._ent.id) + elt.groups._discard(self._ent) + else: + self._ent._pts._RemoveFromGroup(self._ent.id, elt.id) + elt.members._discard(self._ent) + + self._discard(elt) + + +class PTEntry(object): + """An entry in the AFS protection database. + + PTEntry represents a user or group in the AFS protection + database. Each PTEntry is associated with a particular connection + to the protection database. + + PTEntry instances should not be created directly. Instead, use the + "getEntry" method of the PTS object. + + If a PTS connection is authenticated, it should be possible to + change most attributes on a PTEntry. These changes are immediately + propogated to the protection database. + + Attributes: + id: The PTS ID of the entry + name: The username or group name of the entry + count: For users, the number of groups they are a member of; for + groups, the number of users in that group + flags: An integer representation of the flags set on a given + entry + ngroups: The number of additional groups this entry is allowed + to create + nusers: Only meaningful for foreign-cell groups, where it + indicates the ID of the next entry to be created from that + cell. + owner: A PTEntry object representing the owner of a given entry. + creator: A PTEntry object representing the creator of a given + entry. This field is read-only. + + groups: For users, this contains a collection class representing + the set of groups the user is a member of. + users: For groups, this contains a collection class representing + the members of this group. + """ + _attrs = ('id', 'name', 'count', 'flags', 'ngroups', 'nusers') + _entry_attrs = ('owner', 'creator') + + def __new__(cls, pts, id=None, name=None): + if id is None: + if name is None: + raise TypeError('Must specify either a name or an id.') + else: + id = pts._NameToId(name) + + if id not in pts._cache: + if name is None: + name = pts._IdToName(id) + + inst = super(PTEntry, cls).__new__(cls) + inst._pts = pts + inst._id = id + inst._name = name + if id < 0: + inst.members = PTRelationSet(inst) + else: + inst.groups = PTRelationSet(inst) + pts._cache[id] = inst + return pts._cache[id] + + def __repr__(self): + if self.name != '': + return '' % self.name + else: + return '' % self.id + + def _get_id(self): + return self._id + def _set_id(self, val): + del self._pts._cache[self._id] + self._pts._ChangeEntry(self.id, newid=val) + self._id = val + self._pts._cache[val] = self + id = property(_get_id, _set_id) + + def _get_name(self): + return self._name + def _set_name(self, val): + self._pts._ChangeEntry(self.id, newname=val) + self._name = val + name = property(_get_name, _set_name) + + def _get_count(self): + self._loadEntry() + return self._count + count = property(_get_count) + + def _get_flags(self): + self._loadEntry() + return self._flags + def _set_flags(self, val): + self._pts._SetFields(self.id, access=val) + self._flags = val + flags = property(_get_flags, _set_flags) + + def _get_ngroups(self): + self._loadEntry() + return self._ngroups + def _set_ngroups(self, val): + self._pts._SetFields(self.id, groups=val) + self._ngroups = val + ngroups = property(_get_ngroups, _set_ngroups) + + def _get_nusers(self): + self._loadEntry() + return self._nusers + def _set_nusers(self, val): + self._pts._SetFields(self.id, users=val) + self._nusers = val + nusers = property(_get_nusers, _set_nusers) + + def _get_owner(self): + self._loadEntry() + return self._owner + def _set_owner(self, val): + self._pts._ChangeEntry(self.id, newoid=self._pts.getEntry(val).id) + self._owner = val + owner = property(_get_owner, _set_owner) + + def _get_creator(self): + self._loadEntry() + return self._creator + creator = property(_get_creator) + + def _loadEntry(self): + if not hasattr(self, '_flags'): + info = self._pts._ListEntry(self._id) + for field in self._attrs: + setattr(self, '_%s' % field, getattr(info, field)) + for field in self._entry_attrs: + setattr(self, '_%s' % field, self._pts.getEntry(getattr(info, field))) + +class PTS(_pts.PTS): + """A connection to an AFS protection database. + + This class represents a connection to the AFS protection database + for a particular cell. + + Both the umax and gmax attributes can be changed if the connection + was authenticated by a principal on system:administrators for the + cell. + + For sufficiently privileged and authenticated connections, + iterating over a PTS object will yield all entries in the + protection database, in no particular order. + + Args: + cell: The cell to connect to. If None (the default), PTS + connects to the workstations home cell. + sec: The security level to connect with, an integer from 0 to 3: + - 0: unauthenticated connection + - 1: try authenticated, then fall back to unauthenticated + - 2: fail if an authenticated connection can't be established + - 3: same as 2, plus encrypt all traffic to the protection + server + + Attributes: + realm: The Kerberos realm against which this cell authenticates + umax: The maximum user ID currently assigned (the next ID + assigned will be umax + 1) + gmax: The maximum (actually minimum) group ID currently assigned + (the next ID assigned will be gmax - 1, since group IDs are + negative) + """ + def __init__(self, *args, **kwargs): + self._cache = {} + + def __iter__(self): + for pte in self._ListEntries(): + yield self.getEntry(pte.id) + + def getEntry(self, ident): + """Retrieve a particular PTEntry from this cell. + + getEntry accepts either a name or PTS ID as an argument, and + returns a PTEntry object with that name or ID. + """ + if isinstance(ident, PTEntry): + if ident._pts is not self: + raise TypeError("Entry '%s' is from a different cell." % + elt) + return ident + + elif isinstance(ident, basestring): + return PTEntry(self, name=ident) + else: + return PTEntry(self, id=ident) + + def expire(self): + """Flush the cache of PTEntry objects. + + This method will disconnect all PTEntry objects from this PTS + object and flush the cache. + """ + for elt in self._cache.keys(): + del self._cache[elt]._pts + del self._cache[elt] + + def _get_umax(self): + return self._ListMax()[0] + def _set_umax(self, val): + self._SetMaxUserId(val) + umax = property(_get_umax, _set_umax) + + def _get_gmax(self): + return self._ListMax()[1] + def _set_gmax(self, val): + self._SetMaxGroupId(val) + gmax = property(_get_gmax, _set_gmax) -- 1.7.9.5 From f95acfabee62680cab0d953596cc7539585caff8 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Thu, 19 Nov 2009 02:51:51 -0500 Subject: [PATCH 09/16] Add a simple afs._fs module that exposes a whichcell function, and an afs.fs module that simply exports the same whichcell function. Signed-off-by: Evan Broder --- .gitignore | 2 ++ afs/_fs.pyx | 9 +++++++++ afs/afs.pxd | 2 +- afs/fs.py | 2 ++ setup.py | 3 ++- 5 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 afs/_fs.pyx create mode 100644 afs/fs.py diff --git a/.gitignore b/.gitignore index 4fd72a5..93a4719 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ afs/_pts.c afs/_pts.dep afs/_acl.c afs/_acl.dep +afs/_fs.c +afs/_fs.dep *.so build diff --git a/afs/_fs.pyx b/afs/_fs.pyx new file mode 100644 index 0000000..75653e2 --- /dev/null +++ b/afs/_fs.pyx @@ -0,0 +1,9 @@ +from afs cimport * +from afs import pyafs_error + +def whichcell(char* path): + """Determine which AFS cell a particular path is in.""" + cdef char cell[MAXCELLCHARS] + + pioctl_read(path, VIOC_FILE_CELL_NAME, cell, sizeof(cell), 1) + return cell diff --git a/afs/afs.pxd b/afs/afs.pxd index 2b02338..b5180f5 100644 --- a/afs/afs.pxd +++ b/afs/afs.pxd @@ -158,7 +158,7 @@ cdef extern from "afs/vice.h": cdef import from "afs/venus.h": enum: # PIOCTLS to Venus that we use - VIOCGETAL, VIOC_GETVCXSTATUS2, VIOCSETAL + VIOCGETAL, VIOC_GETVCXSTATUS2, VIOCSETAL, VIOC_FILE_CELL_NAME # pioctl doesn't actually have a header, so we have to define it here cdef extern int pioctl(char *, afs_int32, ViceIoctl *, afs_int32) diff --git a/afs/fs.py b/afs/fs.py new file mode 100644 index 0000000..0044c05 --- /dev/null +++ b/afs/fs.py @@ -0,0 +1,2 @@ +import _fs +from _fs import whichcell diff --git a/setup.py b/setup.py index 3e593e3..2a92096 100755 --- a/setup.py +++ b/setup.py @@ -45,8 +45,9 @@ setup( packages=['afs', 'afs.tests'], ext_modules=[ PyAFSExtension("afs.afs"), - PyAFSExtension("afs._pts", libraries=['krb5']), PyAFSExtension("afs._acl"), + PyAFSExtension("afs._fs"), + PyAFSExtension("afs._pts", libraries=['krb5']), ], cmdclass= {"build_ext": build_ext} ) -- 1.7.9.5 From 69ce5cfed0b0efa0d573f1bcdbfad68fa7b2fce3 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Thu, 19 Nov 2009 02:55:34 -0500 Subject: [PATCH 10/16] Add an fs.inafs function for determining if a path is in AFS at all. Signed-off-by: Evan Broder --- afs/fs.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/afs/fs.py b/afs/fs.py index 0044c05..a90cd4c 100644 --- a/afs/fs.py +++ b/afs/fs.py @@ -1,2 +1,13 @@ +import errno import _fs from _fs import whichcell + +def inafs(path): + """Return True if a path is in AFS.""" + try: + whichcell(path) + except OSError, e: + if e.errno in (errno.EINVAL, errno.ENOENT): + return False + + return True -- 1.7.9.5 From b18334d13cd65d8eff268ace564e64d325d01c67 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Thu, 19 Nov 2009 12:03:31 -0500 Subject: [PATCH 11/16] For consistency, use "cdef extern" everywhere instead of "cdef import" Signed-off-by: Evan Broder --- afs/_acl.pyx | 2 +- afs/_pts.pyx | 6 +++--- afs/afs.pxd | 2 +- afs/afs.pyx | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/afs/_acl.pyx b/afs/_acl.pyx index 3f34685..4090ff7 100644 --- a/afs/_acl.pyx +++ b/afs/_acl.pyx @@ -1,7 +1,7 @@ from afs cimport * from afs import pyafs_error -cdef import from "afs/prs_fs.h": +cdef extern from "afs/prs_fs.h": enum: PRSFS_READ, PRSFS_WRITE, PRSFS_INSERT, PRSFS_LOOKUP, PRSFS_DELETE, PRSFS_LOCK, PRSFS_ADMINISTER, diff --git a/afs/_pts.pyx b/afs/_pts.pyx index c347037..420c739 100644 --- a/afs/_pts.pyx +++ b/afs/_pts.pyx @@ -2,7 +2,7 @@ from afs cimport * from afs import pyafs_error import re -cdef import from "afs/ptuser.h": +cdef extern from "afs/ptuser.h": enum: PR_MAXNAMELEN PRGRP @@ -68,11 +68,11 @@ cdef import from "afs/ptuser.h": int ubik_PR_ListEntries(ubik_client *, afs_int32, afs_int32, afs_int32, prentries *, afs_int32 *) int ubik_PR_SetFieldsEntry(ubik_client *, afs_int32, afs_int32, afs_int32, afs_int32, afs_int32, afs_int32, afs_int32, afs_int32) -cdef import from "afs/pterror.h": +cdef extern from "afs/pterror.h": enum: PRNOENT -cdef import from "krb5/krb5.h": +cdef extern from "krb5/krb5.h": struct _krb5_context: pass struct krb5_principal_data: diff --git a/afs/afs.pxd b/afs/afs.pxd index b5180f5..aacba3b 100644 --- a/afs/afs.pxd +++ b/afs/afs.pxd @@ -155,7 +155,7 @@ cdef extern from "afs/vice.h": unsigned short out_size unsigned short in_size -cdef import from "afs/venus.h": +cdef extern from "afs/venus.h": enum: # PIOCTLS to Venus that we use VIOCGETAL, VIOC_GETVCXSTATUS2, VIOCSETAL, VIOC_FILE_CELL_NAME diff --git a/afs/afs.pyx b/afs/afs.pyx index 4fe9e1c..f7f167b 100644 --- a/afs/afs.pyx +++ b/afs/afs.pyx @@ -5,8 +5,8 @@ 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 extern from "netinet/in.h": pass +cdef extern from "afs/vice.h": pass cdef int _init = 0 -- 1.7.9.5 From 41165a6d2c880400afc4b04700d05bd3fa537a42 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Thu, 19 Nov 2009 20:52:29 -0500 Subject: [PATCH 12/16] Rename afs.afs to afs._util so that absolute imports from afs work. Signed-off-by: Evan Broder --- .gitignore | 4 ++-- afs/_acl.pyx | 4 ++-- afs/_fs.pyx | 4 ++-- afs/_pts.pyx | 4 ++-- afs/{afs.pxd => _util.pxd} | 0 afs/{afs.pyx => _util.pyx} | 0 setup.py | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) rename afs/{afs.pxd => _util.pxd} (100%) rename afs/{afs.pyx => _util.pyx} (100%) diff --git a/.gitignore b/.gitignore index 93a4719..4267774 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -afs/afs.c -afs/afs.dep +afs/_util.c +afs/_util.dep afs/_pts.c afs/_pts.dep afs/_acl.c diff --git a/afs/_acl.pyx b/afs/_acl.pyx index 4090ff7..bf1b1f9 100644 --- a/afs/_acl.pyx +++ b/afs/_acl.pyx @@ -1,5 +1,5 @@ -from afs cimport * -from afs import pyafs_error +from afs._util cimport * +from afs._util import pyafs_error cdef extern from "afs/prs_fs.h": enum: diff --git a/afs/_fs.pyx b/afs/_fs.pyx index 75653e2..57ac603 100644 --- a/afs/_fs.pyx +++ b/afs/_fs.pyx @@ -1,5 +1,5 @@ -from afs cimport * -from afs import pyafs_error +from afs._util cimport * +from afs._util import pyafs_error def whichcell(char* path): """Determine which AFS cell a particular path is in.""" diff --git a/afs/_pts.pyx b/afs/_pts.pyx index 420c739..3c10b70 100644 --- a/afs/_pts.pyx +++ b/afs/_pts.pyx @@ -1,5 +1,5 @@ -from afs cimport * -from afs import pyafs_error +from afs._util cimport * +from afs._util import pyafs_error import re cdef extern from "afs/ptuser.h": diff --git a/afs/afs.pxd b/afs/_util.pxd similarity index 100% rename from afs/afs.pxd rename to afs/_util.pxd diff --git a/afs/afs.pyx b/afs/_util.pyx similarity index 100% rename from afs/afs.pyx rename to afs/_util.pyx diff --git a/setup.py b/setup.py index 2a92096..47cf789 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ setup( requires=['Cython'], packages=['afs', 'afs.tests'], ext_modules=[ - PyAFSExtension("afs.afs"), + PyAFSExtension("afs._util"), PyAFSExtension("afs._acl"), PyAFSExtension("afs._fs"), PyAFSExtension("afs._pts", libraries=['krb5']), -- 1.7.9.5 From 0e2974f1af7500997666a21880af9a88947a8125 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Thu, 19 Nov 2009 20:52:48 -0500 Subject: [PATCH 13/16] Always use absolute imports. Signed-off-by: Evan Broder --- afs/acl.py | 6 +++--- afs/fs.py | 4 ++-- afs/pts.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/afs/acl.py b/afs/acl.py index a5293bd..5735438 100644 --- a/afs/acl.py +++ b/afs/acl.py @@ -1,7 +1,7 @@ -import _acl -from _acl import READ, WRITE, INSERT, LOOKUP, DELETE, LOCK, ADMINISTER, \ +import afs._acl +from afs._acl import READ, WRITE, INSERT, LOOKUP, DELETE, LOCK, ADMINISTER, \ USR0, USR1, USR2, USR3, USR4, USR5, USR6, USR7 -from _acl import getCallerAccess +from afs._acl import getCallerAccess _canonical = { "read": "rl", diff --git a/afs/fs.py b/afs/fs.py index a90cd4c..a12b365 100644 --- a/afs/fs.py +++ b/afs/fs.py @@ -1,6 +1,6 @@ import errno -import _fs -from _fs import whichcell +import afs._fs +from afs._fs import whichcell def inafs(path): """Return True if a path is in AFS.""" diff --git a/afs/pts.py b/afs/pts.py index cfbfb7d..47346f7 100644 --- a/afs/pts.py +++ b/afs/pts.py @@ -1,5 +1,5 @@ import collections -import _pts +from afs import _pts class PTRelationSet(collections.MutableSet): """Collection class for the groups/members of a PTEntry. -- 1.7.9.5 From 28bd6503bcc8b1c09f4f12196ea9c0172a450160 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Sun, 22 Nov 2009 18:19:48 -0500 Subject: [PATCH 14/16] Add afs.pts.PTS.getEntryFromKrbname, which converts a Kerberos principal to an AFS principal. (This exposes afs._pts.PTS._Krb5ToAfs) Signed-off-by: Evan Broder --- afs/pts.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/afs/pts.py b/afs/pts.py index 47346f7..8bee97e 100644 --- a/afs/pts.py +++ b/afs/pts.py @@ -354,6 +354,14 @@ class PTS(_pts.PTS): else: return PTEntry(self, id=ident) + def getEntryFromKrbname(self, ident): + """Retrieve a PTEntry matching a given Kerberos v5 principal. + + getEntryFromKrb accepts a krb5 principal, converts it to the + equivalent AFS principal, and returns a PTEntry for that + principal.""" + return self.getEntry(self._Krb5ToAfs(ident)) + def expire(self): """Flush the cache of PTEntry objects. -- 1.7.9.5 From b468bb0ba304169485223cbb146febdece12e4cc Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Sun, 22 Nov 2009 18:20:28 -0500 Subject: [PATCH 15/16] Add a krbname property to afs.pts.PTEntry, which converts the PTEntry's name to a Kerberos-style principal. Signed-off-by: Evan Broder --- afs/pts.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/afs/pts.py b/afs/pts.py index 8bee97e..9c44f3a 100644 --- a/afs/pts.py +++ b/afs/pts.py @@ -248,6 +248,12 @@ class PTEntry(object): self._name = val name = property(_get_name, _set_name) + def _get_krbname(self): + return self._pts._AfsToKrb5(self.name) + def _set_krbname(self, val): + self.name = self._pts._Krb5ToAfs(val) + krbname = property(_get_krbname, _set_krbname) + def _get_count(self): self._loadEntry() return self._count -- 1.7.9.5 From d1b2fea3082cbc0651443dab1c0f5a03f11be435 Mon Sep 17 00:00:00 2001 From: Evan Broder Date: Sun, 22 Nov 2009 18:31:22 -0500 Subject: [PATCH 16/16] Fix typos in afs.acl and afs.fs introduced in 0e2974f1. --- afs/acl.py | 2 +- afs/fs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/afs/acl.py b/afs/acl.py index 5735438..20a416f 100644 --- a/afs/acl.py +++ b/afs/acl.py @@ -1,4 +1,4 @@ -import afs._acl +from afs import _acl from afs._acl import READ, WRITE, INSERT, LOOKUP, DELETE, LOCK, ADMINISTER, \ USR0, USR1, USR2, USR3, USR4, USR5, USR6, USR7 from afs._acl import getCallerAccess diff --git a/afs/fs.py b/afs/fs.py index a12b365..02c2756 100644 --- a/afs/fs.py +++ b/afs/fs.py @@ -1,5 +1,5 @@ import errno -import afs._fs +from afs import _fs from afs._fs import whichcell def inafs(path): -- 1.7.9.5