source: trunk/packages/python-afs/afs/_pts.pyx @ 2971

Last change on this file since 2971 was 2599, checked in by broder, 14 years ago

Import python-afs.

Debathena should eventually be importing PyAFS for
<http://debathena.mit.edu/trac/ticket/395>, so hopefully this is only
temporary.

File size: 21.3 KB
Line 
1from afs._util cimport *
2from afs._util import pyafs_error
3import re
4
5cdef extern from "afs/ptuser.h":
6    enum:
7        PR_MAXNAMELEN
8        PRGRP
9        PRUSERS
10        PRGROUPS
11        ANONYMOUSID
12        PR_SF_ALLBITS
13        PR_SF_NGROUPS
14        PR_SF_NUSERS
15
16    ctypedef char prname[PR_MAXNAMELEN]
17
18    struct namelist:
19        unsigned int namelist_len
20        prname *namelist_val
21
22    struct prlist:
23        unsigned int prlist_len
24        afs_int32 *prlist_val
25
26    struct idlist:
27        unsigned int idlist_len
28        afs_int32 *idlist_val
29
30    struct prcheckentry:
31        afs_int32 flags
32        afs_int32 id
33        afs_int32 owner
34        afs_int32 creator
35        afs_int32 ngroups
36        afs_int32 nusers
37        afs_int32 count
38        char name[PR_MAXNAMELEN]
39
40    struct prlistentries:
41        afs_int32 flags
42        afs_int32 id
43        afs_int32 owner
44        afs_int32 creator
45        afs_int32 ngroups
46        afs_int32 nusers
47        afs_int32 count
48        char name[PR_MAXNAMELEN]
49
50    struct prentries:
51        unsigned int prentries_len
52        prlistentries *prentries_val
53
54    int ubik_PR_NameToID(ubik_client *, afs_int32, namelist *, idlist *)
55    int ubik_PR_IDToName(ubik_client *, afs_int32, idlist *, namelist *)
56    int ubik_PR_INewEntry(ubik_client *, afs_int32, char *, afs_int32, afs_int32)
57    int ubik_PR_NewEntry(ubik_client *, afs_int32, char *, afs_int32, afs_int32, afs_int32 *)
58    int ubik_PR_Delete(ubik_client *, afs_int32, afs_int32)
59    int ubik_PR_AddToGroup(ubik_client *, afs_int32, afs_int32, afs_int32)
60    int ubik_PR_RemoveFromGroup(ubik_client *, afs_int32, afs_int32, afs_int32)
61    int ubik_PR_ListElements(ubik_client *, afs_int32, afs_int32, prlist *, afs_int32 *)
62    int ubik_PR_ListOwned(ubik_client *, afs_int32, afs_int32, prlist *, afs_int32 *)
63    int ubik_PR_ListEntry(ubik_client *, afs_int32, afs_int32, prcheckentry *)
64    int ubik_PR_ChangeEntry(ubik_client *, afs_int32, afs_int32, char *, afs_int32, afs_int32)
65    int ubik_PR_IsAMemberOf(ubik_client *, afs_int32, afs_int32, afs_int32, afs_int32 *)
66    int ubik_PR_ListMax(ubik_client *, afs_int32, afs_int32 *, afs_int32 *)
67    int ubik_PR_SetMax(ubik_client *, afs_int32, afs_int32, afs_int32)
68    int ubik_PR_ListEntries(ubik_client *, afs_int32, afs_int32, afs_int32, prentries *, afs_int32 *)
69    int ubik_PR_SetFieldsEntry(ubik_client *, afs_int32, afs_int32, afs_int32, afs_int32, afs_int32, afs_int32, afs_int32, afs_int32)
70
71cdef extern from "afs/pterror.h":
72    enum:
73        PRNOENT
74
75cdef extern from "krb5/krb5.h":
76    struct _krb5_context:
77        pass
78    struct krb5_principal_data:
79        pass
80
81    ctypedef _krb5_context * krb5_context
82    ctypedef krb5_principal_data * krb5_principal
83
84    ctypedef long krb5_int32
85    ctypedef krb5_int32 krb5_error_code
86    krb5_error_code krb5_init_context(krb5_context *)
87    krb5_error_code krb5_parse_name(krb5_context, char *, krb5_principal *)
88    krb5_error_code krb5_unparse_name(krb5_context, krb5_principal, char **)
89    krb5_error_code krb5_524_conv_principal(krb5_context, krb5_principal, char *, char *, char *)
90    krb5_error_code krb5_425_conv_principal(krb5_context, char *, char *, char *, krb5_principal *)
91    krb5_error_code krb5_get_host_realm(krb5_context, char *, char ***)
92    void krb5_free_host_realm(krb5_context, char **)
93    void krb5_free_principal(krb5_context, krb5_principal)
94    void krb5_free_context(krb5_context)
95
96cdef class PTEntry:
97    cdef public afs_int32 flags
98    cdef public afs_int32 id
99    cdef public afs_int32 owner
100    cdef public afs_int32 creator
101    cdef public afs_int32 ngroups
102    cdef public afs_int32 nusers
103    cdef public afs_int32 count
104    cdef public object name
105
106    def __repr__(self):
107        if self.name != '':
108            return '<PTEntry: %s>' % self.name
109        else:
110            return '<PTEntry: PTS ID %s>' % self.id
111
112cdef int _ptentry_from_c(PTEntry p_entry, prcheckentry * c_entry) except -1:
113    if p_entry is None:
114        raise TypeError
115        return -1
116
117    p_entry.flags = c_entry.flags
118    p_entry.id = c_entry.id
119    p_entry.owner = c_entry.owner
120    p_entry.creator = c_entry.creator
121    p_entry.ngroups = c_entry.ngroups
122    p_entry.nusers = c_entry.nusers
123    p_entry.count = c_entry.count
124    p_entry.name = c_entry.name
125    return 0
126
127cdef int _ptentry_to_c(prcheckentry * c_entry, PTEntry p_entry) except -1:
128    if p_entry is None:
129        raise TypeError
130        return -1
131
132    c_entry.flags = p_entry.flags
133    c_entry.id = p_entry.id
134    c_entry.owner = p_entry.owner
135    c_entry.creator = p_entry.creator
136    c_entry.ngroups = p_entry.ngroups
137    c_entry.nusers = p_entry.nusers
138    c_entry.count = p_entry.count
139    strncpy(c_entry.name, p_entry.name, sizeof(c_entry.name))
140    return 0
141
142cdef object kname_re = re.compile(r'^([^.].*?)(?<!\\)(?:\.(.*?))?(?<!\\)@([^@]*)$')
143
144cdef object kname_parse(fullname):
145    """Parse a krb4-style principal into a name, instance, and realm."""
146    cdef object re_match = kname_re.match(fullname)
147    if not re_match:
148        return None
149    else:
150        princ = re_match.groups()
151        return tuple([re.sub(r'\\(.)', r'\1', x) if x else x for x in princ])
152
153cdef object kname_unparse(name, inst, realm):
154    """Unparse a name, instance, and realm into a single krb4
155    principal string."""
156    name = re.sub('r([.\\@])', r'\\\1', name)
157    inst = re.sub('r([.\\@])', r'\\\1', inst)
158    realm = re.sub(r'([\\@])', r'\\\1', realm)
159    if inst:
160        return '%s.%s@%s' % (name, inst, realm)
161    else:
162        return '%s@%s' % (name, realm)
163
164cdef class PTS:
165    """
166    A PTS object is essentially a handle to talk to the server in a
167    given cell.
168
169    cell defaults to None. If no argument is passed for cell, PTS
170    connects to the home cell.
171
172    sec is the security level, an integer from 0 to 3:
173      - 0: unauthenticated connection
174      - 1: try authenticated, then fall back to unauthenticated
175      - 2: fail if an authenticated connection can't be established
176      - 3: same as 2, plus encrypt all traffic to the protection
177        server
178
179    The realm attribute is the Kerberos realm against which this cell
180    authenticates.
181    """
182    cdef ubik_client * client
183    cdef readonly object cell
184    cdef readonly object realm
185
186    def __cinit__(self, cell=None, sec=1):
187        cdef afs_int32 code
188        cdef afsconf_dir *cdir
189        cdef afsconf_cell info
190        cdef krb5_context context
191        cdef char ** hrealms = NULL
192        cdef char * c_cell
193        cdef ktc_principal prin
194        cdef ktc_token token
195        cdef rx_securityClass *sc
196        cdef rx_connection *serverconns[MAXSERVERS]
197        cdef int i
198
199        initialize_PT_error_table()
200
201        if cell is None:
202            c_cell = NULL
203        else:
204            c_cell = cell
205
206        self.client = NULL
207
208        code = rx_Init(0)
209        if code != 0:
210            raise Exception(code, "Error initializing Rx")
211
212        cdir = afsconf_Open(AFSDIR_CLIENT_ETC_DIRPATH)
213        if cdir is NULL:
214            raise OSError(errno,
215                          "Error opening configuration directory (%s): %s" % \
216                              (AFSDIR_CLIENT_ETC_DIRPATH, strerror(errno)))
217        code = afsconf_GetCellInfo(cdir, c_cell, "afsprot", &info)
218        pyafs_error(code)
219
220        code = krb5_init_context(&context)
221        pyafs_error(code)
222        code = krb5_get_host_realm(context, info.hostName[0], &hrealms)
223        pyafs_error(code)
224        self.realm = hrealms[0]
225        krb5_free_host_realm(context, hrealms)
226        krb5_free_context(context)
227
228        self.cell = info.name
229
230        if sec > 0:
231            strncpy(prin.cell, info.name, sizeof(prin.cell))
232            prin.instance[0] = 0
233            strncpy(prin.name, "afs", sizeof(prin.name))
234
235            code = ktc_GetToken(&prin, &token, sizeof(token), NULL);
236            if code != 0:
237                if sec >= 2:
238                    # No really - we wanted authentication
239                    pyafs_error(code)
240                sec = 0
241            else:
242                if sec == 3:
243                    level = rxkad_crypt
244                else:
245                    level = rxkad_clear
246                sc = rxkad_NewClientSecurityObject(level, &token.sessionKey,
247                                                   token.kvno, token.ticketLen,
248                                                   token.ticket)
249
250        if sec == 0:
251            sc = rxnull_NewClientSecurityObject()
252        else:
253            sec = 2
254
255        memset(serverconns, 0, sizeof(serverconns))
256        for 0 <= i < info.numServers:
257            serverconns[i] = rx_NewConnection(info.hostAddr[i].sin_addr.s_addr,
258                                              info.hostAddr[i].sin_port,
259                                              PRSRV,
260                                              sc,
261                                              sec)
262
263        code = ubik_ClientInit(serverconns, &self.client)
264        pyafs_error(code)
265
266        code = rxs_Release(sc)
267
268    def __dealloc__(self):
269        ubik_ClientDestroy(self.client)
270        rx_Finalize()
271
272    def _NameOrId(self, ident):
273        """
274        Given an identifier, convert it to a PTS ID by looking up the
275        name if it's a string, or otherwise just converting it to an
276        integer.
277        """
278        if isinstance(ident, basestring):
279            return self._NameToId(ident)
280        else:
281            return int(ident)
282
283    def _NameToId(self, name):
284        """
285        Converts a user or group to an AFS ID.
286        """
287        cdef namelist lnames
288        cdef idlist lids
289        cdef afs_int32 code, id = ANONYMOUSID
290        name = name.lower()
291
292        lids.idlist_len = 0
293        lids.idlist_val = NULL
294        lnames.namelist_len = 1
295        lnames.namelist_val = <prname *>malloc(PR_MAXNAMELEN)
296        strncpy(lnames.namelist_val[0], name, PR_MAXNAMELEN)
297        code = ubik_PR_NameToID(self.client, 0, &lnames, &lids)
298        if lids.idlist_val is not NULL:
299            id = lids.idlist_val[0]
300            free(lids.idlist_val)
301        if id == ANONYMOUSID:
302            code = PRNOENT
303        pyafs_error(code)
304        return id
305
306    def _IdToName(self, id):
307        """
308        Convert an AFS ID to the name of a user or group.
309        """
310        cdef namelist lnames
311        cdef idlist lids
312        cdef afs_int32 code
313        cdef char name[PR_MAXNAMELEN]
314
315        lids.idlist_len = 1
316        lids.idlist_val = <afs_int32 *>malloc(sizeof(afs_int32))
317        lids.idlist_val[0] = id
318        lnames.namelist_len = 0
319        lnames.namelist_val = NULL
320        code = ubik_PR_IDToName(self.client, 0, &lids, &lnames)
321        if lnames.namelist_val is not NULL:
322            strncpy(name, lnames.namelist_val[0], sizeof(name))
323            free(lnames.namelist_val)
324        if lids.idlist_val is not NULL:
325            free(lids.idlist_val)
326        if name == str(id):
327            code = PRNOENT
328        pyafs_error(code)
329        return name
330
331    def _CreateUser(self, name, id=None):
332        """
333        Create a new user in the protection database. If an ID is
334        provided, that one will be used.
335        """
336        cdef afs_int32 code
337        cdef afs_int32 cid
338        name = name[:PR_MAXNAMELEN].lower()
339
340        if id is not None:
341            cid = id
342
343        if id is not None:
344            code = ubik_PR_INewEntry(self.client, 0, name, cid, 0)
345        else:
346            code = ubik_PR_NewEntry(self.client, 0, name, 0, 0, &cid)
347
348        pyafs_error(code)
349        return cid
350
351    def _CreateGroup(self, name, owner, id=None):
352        """
353        Create a new group in the protection database. If an ID is
354        provided, that one will be used.
355        """
356        cdef afs_int32 code, cid
357
358        name = name[:PR_MAXNAMELEN].lower()
359        oid = self._NameOrId(owner)
360
361        if id is not None:
362            cid = id
363            code = ubik_PR_INewEntry(self.client, 0, name, cid, oid)
364        else:
365            code = ubik_PR_NewEntry(self.client, 0, name, PRGRP, oid, &cid)
366
367        pyafs_error(code)
368        return cid
369
370    def _Delete(self, ident):
371        """
372        Delete the protection database entry with the provided
373        identifier.
374        """
375        cdef afs_int32 code
376        cdef afs_int32 id = self._NameOrId(ident)
377
378        code = ubik_PR_Delete(self.client, 0, id)
379        pyafs_error(code)
380
381    def _AddToGroup(self, user, group):
382        """
383        Add the given user to the given group.
384        """
385        cdef afs_int32 code
386        cdef afs_int32 uid = self._NameOrId(user), gid = self._NameOrId(group)
387
388        code = ubik_PR_AddToGroup(self.client, 0, uid, gid)
389        pyafs_error(code)
390
391    def _RemoveFromGroup(self, user, group):
392        """
393        Remove the given user from the given group.
394        """
395        cdef afs_int32 code
396        cdef afs_int32 uid = self._NameOrId(user), gid = self._NameOrId(group)
397
398        code = ubik_PR_RemoveFromGroup(self.client, 0, uid, gid)
399        pyafs_error(code)
400
401    def _ListMembers(self, ident):
402        """
403        Get the membership of an entity.
404
405        If id is a group, this returns the users that are in that
406        group.
407
408        If id is a user, this returns the list of groups that user is
409        on.
410
411        This returns a list of PTS IDs.
412        """
413        cdef afs_int32 code, over
414        cdef prlist alist
415        cdef int i
416        cdef object members = []
417
418        cdef afs_int32 id = self._NameOrId(ident)
419
420        alist.prlist_len = 0
421        alist.prlist_val = NULL
422
423        code = ubik_PR_ListElements(self.client, 0, id, &alist, &over)
424
425        if alist.prlist_val is not NULL:
426            for i in range(alist.prlist_len):
427                members.append(alist.prlist_val[i])
428            free(alist.prlist_val)
429
430        pyafs_error(code)
431
432        return members
433
434    def _ListOwned(self, owner):
435        """
436        Get all groups owned by an entity.
437        """
438        cdef afs_int32 code, over
439        cdef prlist alist
440        cdef int i
441        cdef object owned = []
442
443        cdef afs_int32 oid = self._NameOrId(owner)
444
445        alist.prlist_len = 0
446        alist.prlist_val = NULL
447
448        code = ubik_PR_ListOwned(self.client, 0, oid, &alist, &over)
449
450        if alist.prlist_val is not NULL:
451            for i in range(alist.prlist_len):
452                owned.append(alist.prlist_val[i])
453            free(alist.prlist_val)
454
455        pyafs_error(code)
456
457        return owned
458
459    def _ListEntry(self, ident):
460        """
461        Load a PTEntry instance with information about the provided
462        entity.
463        """
464        cdef afs_int32 code
465        cdef prcheckentry centry
466        cdef object entry = PTEntry()
467
468        cdef afs_int32 id = self._NameOrId(ident)
469
470        code = ubik_PR_ListEntry(self.client, 0, id, &centry)
471        pyafs_error(code)
472
473        _ptentry_from_c(entry, &centry)
474        return entry
475
476    def _ChangeEntry(self, ident, newname=None, newid=None, newoid=None):
477        """
478        Change the name, ID, and/or owner of a PTS entity.
479
480        For any of newname, newid, and newoid which aren't specified
481        or ar None, the value isn't changed.
482        """
483        cdef afs_int32 code
484        cdef afs_int32 c_newid = 0, c_newoid = 0
485        cdef char * c_newname
486
487        cdef afs_int32 id = self._NameOrId(ident)
488
489        if newname is None:
490            newname = self._IdToName(id)
491        c_newname = newname
492        if newid is not None:
493            c_newid = newid
494        if newoid is not None:
495            c_newoid = newoid
496
497        code = ubik_PR_ChangeEntry(self.client, 0, id, c_newname, c_newoid, c_newid)
498        pyafs_error(code)
499
500    def _IsAMemberOf(self, user, group):
501        """
502        Return True if the given user is a member of the given group.
503        """
504        cdef afs_int32 code
505        cdef afs_int32 flag
506
507        cdef afs_int32 uid = self._NameOrId(user), gid = self._NameOrId(group)
508
509        code = ubik_PR_IsAMemberOf(self.client, 0, uid, gid, &flag)
510        pyafs_error(code)
511
512        return bool(flag)
513
514    def _ListMax(self):
515        """
516        Return a tuple of the maximum user ID and the maximum group
517        ID currently assigned.
518        """
519        cdef afs_int32 code, uid, gid
520
521        code = ubik_PR_ListMax(self.client, 0, &uid, &gid)
522        pyafs_error(code)
523
524        return (uid, gid)
525
526    def _SetMaxUserId(self, id):
527        """
528        Set the maximum currently assigned user ID (the next
529        automatically assigned UID will be id + 1)
530        """
531        cdef afs_int32 code
532
533        code = ubik_PR_SetMax(self.client, 0, id, 0)
534        pyafs_error(code)
535
536    def _SetMaxGroupId(self, id):
537        """
538        Set the maximum currently assigned user ID (the next
539        automatically assigned UID will be id + 1)
540        """
541        cdef afs_int32 code
542
543        code = ubik_PR_SetMax(self.client, 0, id, PRGRP)
544        pyafs_error(code)
545
546    def _ListEntries(self, users=None, groups=None):
547        """
548        Return a list of PTEntry instances representing all entries in
549        the PRDB.
550
551        Returns just users by default, but can return just users, just
552        groups, or both.
553        """
554        cdef afs_int32 code
555        cdef afs_int32 flag = 0, startindex = 0, nentries, nextstartindex
556        cdef prentries centries
557        cdef unsigned int i
558
559        cdef object entries = []
560
561        if groups is None or users is True:
562            flag |= PRUSERS
563        if groups:
564            flag |= PRGROUPS
565
566        while startindex != -1:
567            centries.prentries_val = NULL
568            centries.prentries_len = 0
569            nextstartindex = -1
570
571            code = ubik_PR_ListEntries(self.client, 0, flag, startindex, &centries, &nextstartindex)
572            if centries.prentries_val is not NULL:
573                for i in range(centries.prentries_len):
574                    e = PTEntry()
575                    _ptentry_from_c(e, <prcheckentry *>&centries.prentries_val[i])
576                    entries.append(e)
577                free(centries.prentries_val)
578            pyafs_error(code)
579
580            startindex = nextstartindex
581
582        return entries
583
584    def _SetFields(self, ident, access=None, groups=None, users=None):
585        """
586        Update the fields for an entry.
587
588        Valid fields are the privacy flags (access), the group quota
589        (groups), or the "foreign user quota" (users), which doesn't
590        actually seem to do anything, but is included for
591        completeness.
592        """
593        cdef afs_int32 code
594        cdef afs_int32 mask = 0, flags = 0, nusers = 0, ngroups = 0
595
596        cdef afs_int32 id = self._NameOrId(ident)
597
598        if access is not None:
599            flags = access
600            mask |= PR_SF_ALLBITS
601        if groups is not None:
602            ngroups = groups
603            mask |= PR_SF_NGROUPS
604        if users is not None:
605            nusers = users
606            mask |= PR_SF_NGROUPS
607
608        code = ubik_PR_SetFieldsEntry(self.client, 0, id, mask, flags, ngroups, nusers, 0, 0)
609        pyafs_error(code)
610
611    def _AfsToKrb5(self, afs_name):
612        """Convert an AFS principal to a Kerberos v5 one."""
613        cdef krb5_context ctx = NULL
614        cdef krb5_principal princ = NULL
615        cdef krb5_error_code code = 0
616        cdef char * krb5_princ = NULL
617        cdef char *name = NULL, *inst = NULL, *realm = NULL
618        cdef object pname, pinst, prealm
619
620        if '@' in afs_name:
621            pname, prealm = afs_name.rsplit('@', 1)
622            prealm = prealm.upper()
623            krb4_name = '%s@%s' % (pname, prealm)
624        else:
625            krb4_name = '%s@%s' % (afs_name, self.realm)
626
627        pname, pinst, prealm = kname_parse(krb4_name)
628        if pname:
629            name = pname
630        if pinst:
631            inst = pinst
632        if prealm:
633            realm = prealm
634
635        code = krb5_init_context(&ctx)
636        try:
637            pyafs_error(code)
638
639            code = krb5_425_conv_principal(ctx, name, inst, realm, &princ)
640            try:
641                pyafs_error(code)
642
643                code = krb5_unparse_name(ctx, princ, &krb5_princ)
644                try:
645                    pyafs_error(code)
646
647                    return krb5_princ
648                finally:
649                    if krb5_princ is not NULL:
650                        free(krb5_princ)
651            finally:
652                if princ is not NULL:
653                    krb5_free_principal(ctx, princ)
654        finally:
655            if ctx is not NULL:
656                krb5_free_context(ctx)
657
658    def _Krb5ToAfs(self, krb5_name):
659        """Convert a Kerberos v5 principal to an AFS one."""
660        cdef krb5_context ctx = NULL
661        cdef krb5_principal k5_princ = NULL
662        cdef char *k4_name, *k4_inst, *k4_realm
663        cdef object afs_princ
664        cdef object afs_name, afs_realm
665
666        k4_name = <char *>malloc(40)
667        k4_name[0] = '\0'
668        k4_inst = <char *>malloc(40)
669        k4_inst[0] = '\0'
670        k4_realm = <char *>malloc(40)
671        k4_realm[0] = '\0'
672
673        code = krb5_init_context(&ctx)
674        try:
675            pyafs_error(code)
676
677            code = krb5_parse_name(ctx, krb5_name, &k5_princ)
678            try:
679                pyafs_error(code)
680
681                code = krb5_524_conv_principal(ctx, k5_princ, k4_name, k4_inst, k4_realm)
682                pyafs_error(code)
683
684                afs_princ = kname_unparse(k4_name, k4_inst, k4_realm)
685                afs_name, afs_realm = afs_princ.rsplit('@', 1)
686
687                if k4_realm == self.realm:
688                    return afs_name
689                else:
690                    return '%s@%s' % (afs_name, afs_realm.lower())
691            finally:
692                if k5_princ is not NULL:
693                    krb5_free_principal(ctx, k5_princ)
694        finally:
695            if ctx is not NULL:
696                krb5_free_context(ctx)
Note: See TracBrowser for help on using the repository browser.