linux-2624-rc5-updates-20080110
[openafs.git] / src / afs / LINUX / osi_groups.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * Implements:
12  * setgroups (syscall)
13  * setpag
14  *
15  */
16 #include <afsconfig.h>
17 #include "afs/param.h"
18 #ifdef LINUX_KEYRING_SUPPORT
19 #include <linux/seq_file.h>
20 #if KEY_TYPE_H_EXISTS
21 #include <linux/key-type.h>
22 #endif
23 #endif
24
25 RCSID
26     ("$Header$");
27
28 #include "afs/sysincludes.h"
29 #include "afsincludes.h"
30 #include "afs/afs_stats.h"      /* statistics */
31 #include "afs/nfsclient.h"
32 #ifdef AFS_LINUX22_ENV
33 #include "h/smp_lock.h"
34 #endif
35
36 #ifdef AFS_LINUX26_ONEGROUP_ENV
37 #define NUMPAGGROUPS 1
38 #else
39 #define NUMPAGGROUPS 2
40 #endif
41
42 #if defined(AFS_LINUX26_ENV)
43 static int
44 afs_setgroups(cred_t **cr, struct group_info *group_info, int change_parent)
45 {
46     struct group_info *old_info;
47
48     AFS_STATCNT(afs_setgroups);
49
50     old_info = (*cr)->cr_group_info;
51     get_group_info(group_info);
52     (*cr)->cr_group_info = group_info;
53     put_group_info(old_info);
54
55     crset(*cr);
56
57     if (change_parent) {
58         old_info = current->parent->group_info;
59         get_group_info(group_info);
60         current->parent->group_info = group_info;
61         put_group_info(old_info);
62     }
63
64     return (0);
65 }
66 #else
67 static int
68 afs_setgroups(cred_t **cr, int ngroups, gid_t * gidset, int change_parent)
69 {
70     int ngrps;
71     int i;
72     gid_t *gp;
73
74     AFS_STATCNT(afs_setgroups);
75
76     if (ngroups > NGROUPS)
77         return EINVAL;
78
79     gp = (*cr)->cr_groups;
80     if (ngroups < NGROUPS)
81         gp[ngroups] = (gid_t) NOGROUP;
82
83     for (i = ngroups; i > 0; i--) {
84         *gp++ = *gidset++;
85     }
86
87     (*cr)->cr_ngroups = ngroups;
88     crset(*cr);
89     return (0);
90 }
91 #endif
92
93 #if defined(AFS_LINUX26_ENV)
94 static struct group_info *
95 afs_getgroups(cred_t * cr)
96 {
97     AFS_STATCNT(afs_getgroups);
98
99     get_group_info(cr->cr_group_info);
100     return cr->cr_group_info;
101 }
102 #else
103 /* Returns number of groups. And we trust groups to be large enough to
104  * hold all the groups.
105  */
106 static int
107 afs_getgroups(cred_t *cr, gid_t *groups)
108 {
109     int i;
110     int n;
111     gid_t *gp;
112
113     AFS_STATCNT(afs_getgroups);
114
115     gp = cr->cr_groups;
116     n = cr->cr_ngroups;
117
118     for (i = 0; (i < n) && (*gp != (gid_t) NOGROUP); i++)
119         *groups++ = *gp++;
120     return i;
121 }
122 #endif
123
124 #if !defined(AFS_LINUX26_ENV)
125 /* Only propogate the PAG to the parent process. Unix's propogate to 
126  * all processes sharing the cred.
127  */
128 int
129 set_pag_in_parent(int pag, int g0, int g1)
130 {
131     int i;
132 #ifdef STRUCT_TASK_STRUCT_HAS_PARENT
133     gid_t *gp = current->parent->groups;
134     int ngroups = current->parent->ngroups;
135 #else
136     gid_t *gp = current->p_pptr->groups;
137     int ngroups = current->p_pptr->ngroups;
138 #endif
139
140     if ((ngroups < 2) || (afs_get_pag_from_groups(gp[0], gp[1]) == NOPAG)) {
141         /* We will have to shift grouplist to make room for pag */
142         if (ngroups + 2 > NGROUPS) {
143             return EINVAL;
144         }
145         for (i = ngroups - 1; i >= 0; i--) {
146             gp[i + 2] = gp[i];
147         }
148         ngroups += 2;
149     }
150     gp[0] = g0;
151     gp[1] = g1;
152     if (ngroups < NGROUPS)
153         gp[ngroups] = NOGROUP;
154
155 #ifdef STRUCT_TASK_STRUCT_HAS_PARENT
156     current->parent->ngroups = ngroups;
157 #else
158     current->p_pptr->ngroups = ngroups;
159 #endif
160     return 0;
161 }
162 #endif
163
164 #if defined(AFS_LINUX26_ENV)
165 int
166 __setpag(cred_t **cr, afs_uint32 pagvalue, afs_uint32 *newpag,
167          int change_parent)
168 {
169     struct group_info *group_info;
170     gid_t g0, g1;
171     struct group_info *tmp;
172     int i;
173 #ifdef AFS_LINUX26_ONEGROUP_ENV
174     int j;
175 #endif
176     int need_space = 0;
177
178     group_info = afs_getgroups(*cr);
179     if (group_info->ngroups < NUMPAGGROUPS
180         ||  afs_get_pag_from_groups(
181 #ifdef AFS_LINUX26_ONEGROUP_ENV
182             group_info
183 #else
184             GROUP_AT(group_info, 0) ,GROUP_AT(group_info, 1)
185 #endif
186                                     ) == NOPAG) 
187         /* We will have to make sure group_info is big enough for pag */
188         need_space = NUMPAGGROUPS;
189
190     tmp = groups_alloc(group_info->ngroups + need_space);
191     
192     *newpag = (pagvalue == -1 ? genpag() : pagvalue);
193 #ifdef AFS_LINUX26_ONEGROUP_ENV
194     for (i = 0, j = 0; i < group_info->ngroups; ++i) {
195         int ths = GROUP_AT(group_info, i);
196         int last = i > 0 ? GROUP_AT(group_info, i-1) : 0;
197         if ((ths >> 24) == 'A')
198             continue;
199         if (last <= *newpag && ths > *newpag) {
200            GROUP_AT(tmp, j) = *newpag;
201            j++;
202         }
203         GROUP_AT(tmp, j) = ths;
204         j++;
205     }
206     if (j != i + need_space)
207         GROUP_AT(tmp, j) = *newpag;
208 #else
209     for (i = 0; i < group_info->ngroups; ++i)
210       GROUP_AT(tmp, i + need_space) = GROUP_AT(group_info, i);
211 #endif
212     put_group_info(group_info);
213     group_info = tmp;
214
215 #ifndef AFS_LINUX26_ONEGROUP_ENV
216     afs_get_groups_from_pag(*newpag, &g0, &g1);
217     GROUP_AT(group_info, 0) = g0;
218     GROUP_AT(group_info, 1) = g1;
219 #endif
220
221     afs_setgroups(cr, group_info, change_parent);
222
223     put_group_info(group_info);
224
225     return 0;
226 }
227
228 #ifdef LINUX_KEYRING_SUPPORT
229 static struct key_type *__key_type_keyring;
230
231 static int
232 install_session_keyring(struct task_struct *task, struct key *keyring)
233 {
234     struct key *old;
235     char desc[20];
236     unsigned long not_in_quota;
237     int code = -EINVAL;
238
239     if (!__key_type_keyring)
240         return code;
241
242     if (!keyring) {
243
244         /* create an empty session keyring */
245         not_in_quota = KEY_ALLOC_IN_QUOTA;
246         sprintf(desc, "_ses.%u", task->tgid);
247
248 #ifdef KEY_ALLOC_NEEDS_STRUCT_TASK
249         keyring = key_alloc(__key_type_keyring, desc,
250                             task->uid, task->gid, task,
251                             (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL,
252                             not_in_quota);
253 #else
254         keyring = key_alloc(__key_type_keyring, desc,
255                             task->uid, task->gid,
256                             (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL,
257                             not_in_quota);
258 #endif
259         if (IS_ERR(keyring)) {
260             code = PTR_ERR(keyring);
261             goto out;
262         }
263     }
264
265     code = key_instantiate_and_link(keyring, NULL, 0, NULL, NULL);
266     if (code < 0) {
267         key_put(keyring);
268         goto out;
269     }
270
271     /* install the keyring */
272     spin_lock_irq(&task->sighand->siglock);
273     old = task->signal->session_keyring;
274     smp_wmb();
275     task->signal->session_keyring = keyring;
276     spin_unlock_irq(&task->sighand->siglock);
277
278     if (old)
279             key_put(old);
280
281 out:
282     return code;
283 }
284 #endif /* LINUX_KEYRING_SUPPORT */
285
286 #else
287 int
288 __setpag(cred_t **cr, afs_uint32 pagvalue, afs_uint32 *newpag,
289          int change_parent)
290 {
291     gid_t *gidset;
292     afs_int32 ngroups, code = 0;
293     int j;
294
295     gidset = (gid_t *) osi_Alloc(NGROUPS * sizeof(gidset[0]));
296     ngroups = afs_getgroups(*cr, gidset);
297
298     if (afs_get_pag_from_groups(gidset[0], gidset[1]) == NOPAG) {
299         /* We will have to shift grouplist to make room for pag */
300         if (ngroups + 2 > NGROUPS) {
301             osi_Free((char *)gidset, NGROUPS * sizeof(int));
302             return EINVAL;
303         }
304         for (j = ngroups - 1; j >= 0; j--) {
305             gidset[j + 2] = gidset[j];
306         }
307         ngroups += 2;
308     }
309     *newpag = (pagvalue == -1 ? genpag() : pagvalue);
310     afs_get_groups_from_pag(*newpag, &gidset[0], &gidset[1]);
311     code = afs_setgroups(cr, ngroups, gidset, change_parent);
312
313     /* If change_parent is set, then we should set the pag in the parent as
314      * well.
315      */
316     if (change_parent && !code) {
317         code = set_pag_in_parent(*newpag, gidset[0], gidset[1]);
318     }
319
320     osi_Free((char *)gidset, NGROUPS * sizeof(int));
321     return code;
322 }
323 #endif
324
325
326 int
327 setpag(cred_t **cr, afs_uint32 pagvalue, afs_uint32 *newpag,
328        int change_parent)
329 {
330     int code;
331
332     AFS_STATCNT(setpag);
333
334     code = __setpag(cr, pagvalue, newpag, change_parent);
335
336 #ifdef LINUX_KEYRING_SUPPORT
337     if (code == 0 && (*cr)->cr_rgid != NFSXLATOR_CRED) {
338         (void) install_session_keyring(current, NULL);
339
340         if (current->signal->session_keyring) {
341             struct key *key;
342             key_perm_t perm;
343
344             perm = KEY_POS_VIEW | KEY_POS_SEARCH;
345             perm |= KEY_USR_VIEW | KEY_USR_SEARCH;
346
347 #ifdef KEY_ALLOC_NEEDS_STRUCT_TASK
348             key = key_alloc(&key_type_afs_pag, "_pag", 0, 0, current, perm, 1);
349 #else
350             key = key_alloc(&key_type_afs_pag, "_pag", 0, 0, perm, 1);
351 #endif
352
353             if (!IS_ERR(key)) {
354                 key_instantiate_and_link(key, (void *) newpag, sizeof(afs_uint32),
355                                          current->signal->session_keyring, NULL);
356                 key_put(key);
357             }
358         }
359     }
360 #endif /* LINUX_KEYRING_SUPPORT */
361
362     return code;
363 }
364
365
366 /* Intercept the standard system call. */
367 extern asmlinkage long (*sys_setgroupsp) (int gidsetsize, gid_t * grouplist);
368 asmlinkage long
369 afs_xsetgroups(int gidsetsize, gid_t * grouplist)
370 {
371     long code;
372     cred_t *cr = crref();
373     afs_uint32 junk;
374     int old_pag;
375
376     lock_kernel();
377     old_pag = PagInCred(cr);
378     crfree(cr);
379     unlock_kernel();
380
381     code = (*sys_setgroupsp) (gidsetsize, grouplist);
382     if (code) {
383         return code;
384     }
385
386     lock_kernel();
387     cr = crref();
388     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
389         /* re-install old pag if there's room. */
390         code = __setpag(&cr, old_pag, &junk, 0);
391     }
392     crfree(cr);
393     unlock_kernel();
394
395     /* Linux syscall ABI returns errno as negative */
396     return (-code);
397 }
398
399 #if defined(AFS_LINUX24_ENV)
400 /* Intercept the standard uid32 system call. */
401 extern asmlinkage long (*sys_setgroups32p) (int gidsetsize, gid_t * grouplist);
402 asmlinkage long
403 afs_xsetgroups32(int gidsetsize, gid_t * grouplist)
404 {
405     long code;
406     cred_t *cr = crref();
407     afs_uint32 junk;
408     int old_pag;
409
410     lock_kernel();
411     old_pag = PagInCred(cr);
412     crfree(cr);
413     unlock_kernel();
414
415     code = (*sys_setgroups32p) (gidsetsize, grouplist);
416
417     if (code) {
418         return code;
419     }
420
421     lock_kernel();
422     cr = crref();
423     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
424         /* re-install old pag if there's room. */
425         code = __setpag(&cr, old_pag, &junk, 0);
426     }
427     crfree(cr);
428     unlock_kernel();
429
430     /* Linux syscall ABI returns errno as negative */
431     return (-code);
432 }
433 #endif
434
435 #if defined(AFS_PPC64_LINUX20_ENV)
436 /* Intercept the uid16 system call as used by 32bit programs. */
437 extern long (*sys32_setgroupsp)(int gidsetsize, gid_t *grouplist);
438 asmlinkage long afs32_xsetgroups(int gidsetsize, gid_t *grouplist)
439 {
440     long code;
441     cred_t *cr = crref();
442     afs_uint32 junk;
443     int old_pag;
444     
445     lock_kernel();
446     old_pag = PagInCred(cr);
447     crfree(cr);
448     unlock_kernel();
449     
450     code = (*sys32_setgroupsp)(gidsetsize, grouplist);
451     if (code) {
452         return code;
453     }
454     
455     lock_kernel();
456     cr = crref();
457     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
458         /* re-install old pag if there's room. */
459         code = __setpag(&cr, old_pag, &junk, 0);
460     }
461     crfree(cr);
462     unlock_kernel();
463     
464     /* Linux syscall ABI returns errno as negative */
465     return (-code);
466 }
467 #endif
468
469 #if defined(AFS_SPARC64_LINUX20_ENV) || defined(AFS_AMD64_LINUX20_ENV)
470 /* Intercept the uid16 system call as used by 32bit programs. */
471 extern long (*sys32_setgroupsp) (int gidsetsize, u16 * grouplist);
472 asmlinkage long
473 afs32_xsetgroups(int gidsetsize, u16 * grouplist)
474 {
475     long code;
476     cred_t *cr = crref();
477     afs_uint32 junk;
478     int old_pag;
479     
480     lock_kernel();
481     old_pag = PagInCred(cr);
482     crfree(cr);
483     unlock_kernel();
484     
485     code = (*sys32_setgroupsp) (gidsetsize, grouplist);
486     if (code) {
487         return code;
488     }
489     
490     lock_kernel();
491     cr = crref();
492     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
493         /* re-install old pag if there's room. */
494         code = __setpag(&cr, old_pag, &junk, 0);
495     }
496     crfree(cr);
497     unlock_kernel();
498     
499     /* Linux syscall ABI returns errno as negative */
500     return (-code);
501 }
502
503 #ifdef AFS_LINUX24_ENV
504 /* Intercept the uid32 system call as used by 32bit programs. */
505 extern long (*sys32_setgroups32p) (int gidsetsize, gid_t * grouplist);
506 asmlinkage long
507 afs32_xsetgroups32(int gidsetsize, gid_t * grouplist)
508 {
509     long code;
510     cred_t *cr = crref();
511     afs_uint32 junk;
512     int old_pag;
513
514     lock_kernel();
515     old_pag = PagInCred(cr);
516     crfree(cr);
517     unlock_kernel();
518
519     code = (*sys32_setgroups32p) (gidsetsize, grouplist);
520     if (code) {
521         return code;
522     }
523
524     lock_kernel();
525     cr = crref();
526     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
527         /* re-install old pag if there's room. */
528         code = __setpag(&cr, old_pag, &junk, 0);
529     }
530     crfree(cr);
531     unlock_kernel();
532
533     /* Linux syscall ABI returns errno as negative */
534     return (-code);
535 }
536 #endif
537 #endif
538
539
540 #ifdef LINUX_KEYRING_SUPPORT
541 static void afs_pag_describe(const struct key *key, struct seq_file *m)
542 {
543     seq_puts(m, key->description);
544
545     seq_printf(m, ": %u", key->datalen);
546 }
547
548 static int afs_pag_instantiate(struct key *key, const void *data, size_t datalen)
549 {
550     int code;
551     afs_uint32 *userpag, pag = NOPAG;
552     int g0, g1;
553
554     if (key->uid != 0 || key->gid != 0)
555         return -EPERM;
556
557     code = -EINVAL;
558     get_group_info(current->group_info);
559
560     if (datalen != sizeof(afs_uint32) || !data)
561         goto error;
562
563     if (current->group_info->ngroups < NUMPAGGROUPS)
564         goto error;
565
566     /* ensure key being set matches current pag */
567 #ifdef AFS_LINUX26_ONEGROUP_ENV
568     pag = afs_get_pag_from_groups(current->group_info);
569 #else
570     g0 = GROUP_AT(current->group_info, 0);
571     g1 = GROUP_AT(current->group_info, 1);
572
573     pag = afs_get_pag_from_groups(g0, g1);
574 #endif
575     if (pag == NOPAG)
576         goto error;
577
578     userpag = (afs_uint32 *) data;
579     if (*userpag != pag)
580         goto error;
581
582     key->payload.value = (unsigned long) *userpag;
583     key->datalen = sizeof(afs_uint32);
584     code = 0;
585
586 error:
587     put_group_info(current->group_info);
588     return code;
589 }
590
591 static int afs_pag_match(const struct key *key, const void *description)
592 {
593         return strcmp(key->description, description) == 0;
594 }
595
596 static void afs_pag_destroy(struct key *key)
597 {
598     afs_uint32 pag = key->payload.value;
599     struct unixuser *pu;
600     int locked = ISAFS_GLOCK();
601
602     if (!locked)
603         AFS_GLOCK();
604     pu = afs_FindUser(pag, -1, READ_LOCK);
605     if (pu) {
606         pu->ct.EndTimestamp = 0;
607         pu->tokenTime = 0;
608         afs_PutUser(pu, READ_LOCK);
609     }
610     if (!locked)
611         AFS_GUNLOCK();
612 }
613
614 struct key_type key_type_afs_pag =
615 {
616     .name        = "afs_pag",
617     .describe    = afs_pag_describe,
618     .instantiate = afs_pag_instantiate,
619     .match       = afs_pag_match,
620     .destroy     = afs_pag_destroy,
621 };
622
623 #ifdef EXPORTED_TASKLIST_LOCK
624 extern rwlock_t tasklist_lock __attribute__((weak));
625 #endif
626
627 void osi_keyring_init(void)
628 {
629     struct task_struct *p;
630 #ifdef EXPORTED_TASKLIST_LOCK
631     if (&tasklist_lock)
632       read_lock(&tasklist_lock);
633 #endif
634 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
635 #ifdef EXPORTED_TASKLIST_LOCK
636     else
637 #endif
638       rcu_read_lock();
639 #endif
640     p = find_task_by_pid(1);
641     if (p && p->user->session_keyring)
642         __key_type_keyring = p->user->session_keyring->type;
643 #ifdef EXPORTED_TASKLIST_LOCK
644     if (&tasklist_lock)
645        read_unlock(&tasklist_lock);
646 #endif
647 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
648 #ifdef EXPORTED_TASKLIST_LOCK
649     else
650 #endif
651       rcu_read_unlock();
652 #endif
653
654     register_key_type(&key_type_afs_pag);
655 }
656
657 void osi_keyring_shutdown(void)
658 {
659     unregister_key_type(&key_type_afs_pag);
660 }
661
662 #else
663 void osi_keyring_init(void)
664 {
665         return;
666 }
667
668 void osi_keyring_shutdown(void)
669 {
670         return;
671 }
672 #endif