Merge pull request #2 from dehnert/master
[invirt/packages/python-afs.git] / afs / _pts.pyx
1 from afs._util cimport *
2 from afs._util import pyafs_error
3 import re
4
5 cdef 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
71 cdef extern from "afs/pterror.h":
72     enum:
73         PRNOENT
74
75 cdef 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
96 cdef 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
112 cdef 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
127 cdef 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
142 cdef object kname_re = re.compile(r'^([^.].*?)(?<!\\)(?:\.(.*?))?(?<!\\)@([^@]*)$')
143
144 cdef 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
153 cdef 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
164 cdef 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)