Solaris: Don't access lbolt directly
[openafs.git] / src / afs / afs_user.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  */
13 #include <afsconfig.h>
14 #include "afs/param.h"
15
16
17 #include "afs/stds.h"
18 #include "afs/sysincludes.h"    /* Standard vendor system headers */
19
20 #if !defined(UKERNEL)
21 #if !defined(AFS_LINUX20_ENV)
22 #include <net/if.h>
23 #endif
24 #include <netinet/in.h>
25
26 #ifdef AFS_SGI62_ENV
27 #include "h/hashing.h"
28 #endif
29 #if !defined(AFS_HPUX110_ENV) && !defined(AFS_LINUX20_ENV) && !defined(AFS_DARWIN60_ENV)
30 #include <netinet/in_var.h>
31 #endif /* ! AFS_HPUX110_ENV */
32 #endif /* !defined(UKERNEL) */
33
34 #include "afsincludes.h"        /* Afs-based standard headers */
35 #include "afs/afs_stats.h"      /* afs statistics */
36
37 #if     defined(AFS_SUN56_ENV)
38 #include <inet/led.h>
39 #include <inet/common.h>
40 #if   defined(AFS_SUN58_ENV)
41 #include <netinet/ip6.h>
42 #endif
43 #include <inet/ip.h>
44 #endif
45
46
47 /* Exported variables */
48 afs_rwlock_t afs_xuser;
49 struct unixuser *afs_users[NUSERS];
50
51
52 #ifndef AFS_PAG_MANAGER
53 /* Forward declarations */
54 void afs_ResetAccessCache(afs_int32 uid, int alock);
55
56 /*
57  * Called with afs_xuser, afs_xserver and afs_xconn locks held, to delete
58  * appropriate conn structures for au
59  */
60 static void
61 RemoveUserConns(register struct unixuser *au)
62 {
63     register int i;
64     register struct server *ts;
65     register struct srvAddr *sa;
66     register struct afs_conn *tc, **lc;
67
68     AFS_STATCNT(RemoveUserConns);
69     for (i = 0; i < NSERVERS; i++) {
70         for (ts = afs_servers[i]; ts; ts = ts->next) {
71             for (sa = ts->addr; sa; sa = sa->next_sa) {
72                 lc = &sa->conns;
73                 for (tc = *lc; tc; lc = &tc->next, tc = *lc) {
74                     if (tc->user == au && tc->refCount == 0) {
75                         *lc = tc->next;
76                         AFS_GUNLOCK();
77                         rx_DestroyConnection(tc->id);
78                         AFS_GLOCK();
79                         afs_osi_Free(tc, sizeof(struct afs_conn));
80                         break;  /* at most one instance per server */
81                     }           /*Found unreferenced connection for user */
82                 }               /*For each connection on the server */
83             }
84         }                       /*For each server on chain */
85     }                           /*For each chain */
86
87 }                               /*RemoveUserConns */
88 #endif /* !AFS_PAG_MANAGER */
89
90
91 /* Called from afs_Daemon to garbage collect unixusers no longer using system,
92  * and their conns.  The aforce parameter tells the function to flush all
93  * *unauthenticated* conns, no matter what their expiration time; it exists
94  * because after we choose our final rx epoch, we want to stop using calls with
95  * other epochs as soon as possible (old file servers act bizarrely when they
96  * see epoch changes).
97  */
98 void
99 afs_GCUserData(int aforce)
100 {
101     register struct unixuser *tu, **lu, *nu;
102     register int i;
103     afs_int32 now, delFlag;
104
105     AFS_STATCNT(afs_GCUserData);
106     /* Obtain locks in valid order */
107     ObtainWriteLock(&afs_xuser, 95);
108 #ifndef AFS_PAG_MANAGER
109     ObtainReadLock(&afs_xserver);
110     ObtainWriteLock(&afs_xconn, 96);
111 #endif
112     now = osi_Time();
113     for (i = 0; i < NUSERS; i++) {
114         for (lu = &afs_users[i], tu = *lu; tu; tu = nu) {
115             delFlag = 0;        /* should we delete this dude? */
116             /* Don't garbage collect users in use now (refCount) */
117             if (tu->refCount == 0) {
118                 if (tu->states & UHasTokens) {
119                     /*
120                      * Give ourselves a little extra slack, in case we
121                      * reauthenticate
122                      */
123                     if (tu->ct.EndTimestamp < now - NOTOKTIMEOUT)
124                         delFlag = 1;
125                 } else {
126                     if (aforce || (tu->tokenTime < now - NOTOKTIMEOUT))
127                         delFlag = 1;
128                 }
129             }
130             nu = tu->next;
131             if (delFlag) {
132                 *lu = tu->next;
133 #ifndef AFS_PAG_MANAGER
134                 RemoveUserConns(tu);
135 #endif
136                 if (tu->stp)
137                     afs_osi_Free(tu->stp, tu->stLen);
138                 if (tu->exporter)
139                     EXP_RELE(tu->exporter);
140                 afs_osi_Free(tu, sizeof(struct unixuser));
141             } else {
142                 lu = &tu->next;
143             }
144         }
145     }
146 #ifndef AFS_PAG_MANAGER
147     ReleaseWriteLock(&afs_xconn);
148 #endif
149 #ifndef AFS_PAG_MANAGER
150     ReleaseReadLock(&afs_xserver);
151 #endif
152     ReleaseWriteLock(&afs_xuser);
153
154 }                               /*afs_GCUserData */
155
156
157 #ifndef AFS_PAG_MANAGER
158 /*
159  * Check for unixusers who encountered bad tokens, and reset the access
160  * cache for these guys.  Can't do this when token expiration detected,
161  * since too many locks are set then.
162  */
163 void
164 afs_CheckTokenCache(void)
165 {
166     register int i;
167     register struct unixuser *tu;
168     afs_int32 now;
169
170     AFS_STATCNT(afs_CheckCacheResets);
171     ObtainReadLock(&afs_xvcache);
172     ObtainReadLock(&afs_xuser);
173     now = osi_Time();
174     for (i = 0; i < NUSERS; i++) {
175         for (tu = afs_users[i]; tu; tu = tu->next) {
176             register afs_int32 uid;
177
178             /*
179              * If tokens are still good and user has Kerberos tickets,
180              * check expiration
181              */
182             if (!(tu->states & UTokensBad) && tu->vid != UNDEFVID) {
183                 if (tu->ct.EndTimestamp < now) {
184                     /*
185                      * This token has expired, warn users and reset access
186                      * cache.
187                      */
188 #ifdef notdef
189                     /* I really hate this message - MLK */
190                     afs_warn
191                         ("afs: Tokens for user of AFS id %d for cell %s expired now\n",
192                          tu->vid, afs_GetCell(tu->cell)->cellName);
193 #endif
194                     tu->states |= (UTokensBad | UNeedsReset);
195                 }
196             }
197             if (tu->states & UNeedsReset) {
198                 tu->states &= ~UNeedsReset;
199                 uid = tu->uid;
200                 afs_ResetAccessCache(uid, 0);
201             }
202         }
203     }
204     ReleaseReadLock(&afs_xuser);
205     ReleaseReadLock(&afs_xvcache);
206
207 }                               /*afs_CheckTokenCache */
208
209
210 void
211 afs_ResetAccessCache(afs_int32 uid, int alock)
212 {
213     register int i;
214     register struct vcache *tvc;
215     struct axscache *ac;
216
217     AFS_STATCNT(afs_ResetAccessCache);
218     if (alock)
219         ObtainReadLock(&afs_xvcache);
220     for (i = 0; i < VCSIZE; i++) {
221         for (tvc = afs_vhashT[i]; tvc; tvc = tvc->hnext) {
222             /* really should do this under cache write lock, but that.
223              * is hard to under locking hierarchy */
224             if (tvc->Access && (ac = afs_FindAxs(tvc->Access, uid))) {
225                 afs_RemoveAxs(&tvc->Access, ac);
226             }
227         }
228     }
229     if (alock)
230         ReleaseReadLock(&afs_xvcache);
231
232 }                               /*afs_ResetAccessCache */
233
234
235 /*
236  * Ensure all connections make use of new tokens.  Discard incorrectly-cached
237  * access info.
238  */
239 void
240 afs_ResetUserConns(register struct unixuser *auser)
241 {
242     int i;
243     struct srvAddr *sa;
244     struct afs_conn *tc;
245
246     AFS_STATCNT(afs_ResetUserConns);
247     ObtainReadLock(&afs_xsrvAddr);
248     ObtainWriteLock(&afs_xconn, 98);
249
250     for (i = 0; i < NSERVERS; i++) {
251         for (sa = afs_srvAddrs[i]; sa; sa = sa->next_bkt) {
252             for (tc = sa->conns; tc; tc = tc->next) {
253                 if (tc->user == auser) {
254                     tc->forceConnectFS = 1;
255                 }
256             }
257         }
258     }
259
260     ReleaseWriteLock(&afs_xconn);
261     ReleaseReadLock(&afs_xsrvAddr);
262     afs_ResetAccessCache(auser->uid, 1);
263     auser->states &= ~UNeedsReset;
264 }                               /*afs_ResetUserConns */
265 #endif /* !AFS_PAG_MANAGER */
266
267
268 struct unixuser *
269 afs_FindUser(afs_int32 auid, afs_int32 acell, afs_int32 locktype)
270 {
271     register struct unixuser *tu;
272     register afs_int32 i;
273
274     AFS_STATCNT(afs_FindUser);
275     i = UHash(auid);
276     ObtainWriteLock(&afs_xuser, 99);
277     for (tu = afs_users[i]; tu; tu = tu->next) {
278         if (tu->uid == auid && ((tu->cell == acell) || (acell == -1))) {
279             tu->refCount++;
280             ReleaseWriteLock(&afs_xuser);
281             return tu;
282         }
283     }
284     ReleaseWriteLock(&afs_xuser);
285     return NULL;
286
287 }                               /*afs_FindUser */
288
289
290 /*------------------------------------------------------------------------
291  * EXPORTED afs_ComputePAGStats
292  *
293  * Description:
294  *      Compute a set of stats concerning PAGs used by this machine.
295  *
296  * Arguments:
297  *      None.
298  *
299  * Returns:
300  *      Nothing.
301  *
302  * Environment:
303  *      The results are put in the structure responsible for keeping
304  *      detailed CM stats.  Note: entries corresponding to a single PAG
305  *      will appear on the identical hash chain, so sweeping the chain
306  *      will find all entries related to a single PAG.
307  *
308  * Side Effects:
309  *      As advertised.
310  *------------------------------------------------------------------------*/
311
312 void
313 afs_ComputePAGStats(void)
314 {
315     register struct unixuser *currPAGP; /*Ptr to curr PAG */
316     register struct unixuser *cmpPAGP;  /*Ptr to PAG being compared */
317     register struct afs_stats_AuthentInfo *authP;       /*Ptr to stats area */
318     int curr_Record;            /*Curr record */
319     int currChain;              /*Curr hash chain */
320     int currChainLen;           /*Length of curr hash chain */
321     int currPAGRecords;         /*# records in curr PAG */
322
323     /*
324      * Lock out everyone else from scribbling on the PAG entries.
325      */
326     ObtainReadLock(&afs_xuser);
327
328     /*
329      * Initialize the tallies, then sweep through each hash chain.  We
330      * can't bzero the structure, since some fields are cumulative over
331      * the CM's lifetime.
332      */
333     curr_Record = 0;
334     authP = &(afs_stats_cmfullperf.authent);
335     authP->curr_PAGs = 0;
336     authP->curr_Records = 0;
337     authP->curr_AuthRecords = 0;
338     authP->curr_UnauthRecords = 0;
339     authP->curr_MaxRecordsInPAG = 0;
340     authP->curr_LongestChain = 0;
341
342     for (currChain = 0; currChain < NUSERS; currChain++) {
343         currChainLen = 0;
344         for (currPAGP = afs_users[currChain]; currPAGP;
345              currPAGP = currPAGP->next) {
346             /*
347              * Bump the number of records on this current chain, along with
348              * the total number of records in existence.
349              */
350             currChainLen++;
351             curr_Record++;
352             /*
353              * We've found a previously-uncounted PAG.  If it's been deleted
354              * but just not garbage-collected yet, we step over it.
355              */
356             if (currPAGP->vid == UNDEFVID)
357                 continue;
358
359             /*
360              * If this PAG record has already been ``counted', namely
361              * associated with a PAG and tallied, clear its bit and move on.
362              */
363             (authP->curr_Records)++;
364             if (currPAGP->states & UPAGCounted) {
365                 currPAGP->states &= ~UPAGCounted;
366                 continue;
367             }
368
369
370
371             /*We've counted this one already */
372             /*
373              * Jot initial info down, then sweep down the rest of the hash
374              * chain, looking for matching PAG entries.  Note: to properly
375              * ``count'' the current record, we first compare it to itself
376              * in the following loop.
377              */
378             (authP->curr_PAGs)++;
379             currPAGRecords = 0;
380
381             for (cmpPAGP = currPAGP; cmpPAGP; cmpPAGP = cmpPAGP->next) {
382                 if (currPAGP->uid == cmpPAGP->uid) {
383                     /*
384                      * The records belong to the same PAG.  First, mark the
385                      * new record as ``counted'' and bump the PAG size.
386                      * Then, record the state of its ticket, if any.
387                      */
388                     cmpPAGP->states |= UPAGCounted;
389                     currPAGRecords++;
390                     if ((cmpPAGP->states & UHasTokens)
391                         && !(cmpPAGP->states & UTokensBad))
392                         (authP->curr_AuthRecords)++;
393                     else
394                         (authP->curr_UnauthRecords)++;
395                 }               /*Records belong to same PAG */
396             }                   /*Compare to rest of PAG records in chain */
397
398             /*
399              * In the above comparisons, the current PAG record has been
400              * marked as counted.  Erase this mark before moving on.
401              */
402             currPAGP->states &= ~UPAGCounted;
403
404             /*
405              * We've compared our current PAG record with all remaining
406              * PAG records in the hash chain.  Update our tallies, and
407              * perhaps even our lifetime high water marks.  After that,
408              * remove our search mark and advance to the next comparison
409              * pair.
410              */
411             if (currPAGRecords > authP->curr_MaxRecordsInPAG) {
412                 authP->curr_MaxRecordsInPAG = currPAGRecords;
413                 if (currPAGRecords > authP->HWM_MaxRecordsInPAG)
414                     authP->HWM_MaxRecordsInPAG = currPAGRecords;
415             }
416         }                       /*Sweep a hash chain */
417
418         /*
419          * If the chain we just finished zipping through is the longest we've
420          * seen yet, remember this fact before advancing to the next chain.
421          */
422         if (currChainLen > authP->curr_LongestChain) {
423             authP->curr_LongestChain = currChainLen;
424             if (currChainLen > authP->HWM_LongestChain)
425                 authP->HWM_LongestChain = currChainLen;
426         }
427
428     }                           /*For each hash chain in afs_user */
429
430     /*
431      * Now that we've counted everything up, we can consider all-time
432      * numbers.
433      */
434     if (authP->curr_PAGs > authP->HWM_PAGs)
435         authP->HWM_PAGs = authP->curr_PAGs;
436     if (authP->curr_Records > authP->HWM_Records)
437         authP->HWM_Records = authP->curr_Records;
438
439     /*
440      * People are free to manipulate the PAG structures now.
441      */
442     ReleaseReadLock(&afs_xuser);
443
444 }                               /*afs_ComputePAGStats */
445
446
447 struct unixuser *
448 afs_GetUser(register afs_int32 auid, afs_int32 acell, afs_int32 locktype)
449 {
450     register struct unixuser *tu, *pu = 0;
451     register afs_int32 i;
452     register afs_int32 RmtUser = 0;
453
454     AFS_STATCNT(afs_GetUser);
455     i = UHash(auid);
456     ObtainWriteLock(&afs_xuser, 104);
457     for (tu = afs_users[i]; tu; tu = tu->next) {
458         if (tu->uid == auid) {
459             RmtUser = 0;
460             pu = NULL;
461             if (tu->exporter) {
462                 RmtUser = 1;
463                 pu = tu;
464             }
465             if (tu->cell == -1 && acell != -1) {
466                 /* Here we setup the real cell for the client */
467                 tu->cell = acell;
468                 tu->refCount++;
469                 ReleaseWriteLock(&afs_xuser);
470                 return tu;
471             } else if (tu->cell == acell || acell == -1) {
472                 tu->refCount++;
473                 ReleaseWriteLock(&afs_xuser);
474                 return tu;
475             }
476         }
477     }
478     tu = (struct unixuser *)afs_osi_Alloc(sizeof(struct unixuser));
479 #ifndef AFS_NOSTATS
480     afs_stats_cmfullperf.authent.PAGCreations++;
481 #endif /* AFS_NOSTATS */
482     memset(tu, 0, sizeof(struct unixuser));
483     tu->next = afs_users[i];
484     afs_users[i] = tu;
485     if (RmtUser) {
486         /*
487          * This is for the case where an additional unixuser struct is
488          * created because the remote client is accessing a different cell;
489          * we simply rerecord relevant information from the original
490          * structure
491          */
492         if (pu && pu->exporter) {
493             (void)EXP_HOLD(tu->exporter = pu->exporter);
494         }
495     }
496     tu->uid = auid;
497     tu->cell = acell;
498     tu->vid = UNDEFVID;
499     tu->refCount = 1;
500     tu->tokenTime = osi_Time();
501     ReleaseWriteLock(&afs_xuser);
502     return tu;
503
504 }                               /*afs_GetUser */
505
506
507 void
508 afs_PutUser(register struct unixuser *au, afs_int32 locktype)
509 {
510     AFS_STATCNT(afs_PutUser);
511     --au->refCount;
512 }                               /*afs_PutUser */
513
514
515 /*
516  * Set the primary flag on a unixuser structure, ensuring that exactly one
517  * dude has the flag set at any time for a particular unix uid.
518  */
519 void
520 afs_SetPrimary(register struct unixuser *au, register int aflag)
521 {
522     register struct unixuser *tu;
523     register int i;
524     struct unixuser *pu;
525
526     AFS_STATCNT(afs_SetPrimary);
527     i = UHash(au->uid);
528     pu = NULL;
529     ObtainWriteLock(&afs_xuser, 105);
530     /*
531      * See if anyone is this uid's primary cell yet; recording in pu the
532      * corresponding user
533      */
534     for (tu = afs_users[i]; tu; tu = tu->next) {
535         if (tu->uid == au->uid && (tu->states & UPrimary)) {
536             pu = tu;
537         }
538     }
539     if (pu && !(pu->states & UHasTokens)) {
540         /*
541          * Primary user has unlogged, don't treat him as primary any longer;
542          * note that we want to treat him as primary until now, so that
543          * people see a primary identity until now.
544          */
545         pu->states &= ~UPrimary;
546         pu = NULL;
547     }
548     if (aflag == 1) {
549         /* setting au to be primary */
550         if (pu)
551             pu->states &= ~UPrimary;
552         au->states |= UPrimary;
553     } else if (aflag == 0) {
554         /* we don't know if we're supposed to be primary or not */
555         if (!pu || au == pu) {
556             au->states |= UPrimary;
557         } else
558             au->states &= ~UPrimary;
559     }
560     ReleaseWriteLock(&afs_xuser);
561
562 }                               /*afs_SetPrimary */
563
564
565 #if AFS_GCPAGS
566
567 /*
568  * Called by osi_TraverseProcTable (from afs_GCPAGs) for each 
569  * process in the system.
570  * If the specified process uses a PAG, clear that PAG's temporary
571  * 'deleteme' flag.
572  */
573
574 /*
575  * This variable keeps track of the number of UID-base
576  * tokens in the afs_users table. When it's zero
577  * the per process loop in GCPAGs doesn't have to
578  * check processes without pags against the afs_users table.
579  */
580 static afs_int32 afs_GCPAGs_UIDBaseTokenCount = 0;
581
582 /*
583  * These variables keep track of the number of times
584  * afs_GCPAGs_perproc_func() is called.  If it is not called at all when
585  * walking the process table, there is something wrong and we should not
586  * prematurely expire any tokens.
587  */
588 static size_t afs_GCPAGs_perproc_count = 0;
589 static size_t afs_GCPAGs_cred_count = 0;
590
591 /*
592  * LOCKS: afs_GCPAGs_perproc_func requires write lock on afs_xuser
593  */
594 #if !defined(LINUX_KEYRING_SUPPORT) && (!defined(STRUCT_TASK_HAS_CRED) || defined(EXPORTED_RCU_READ_LOCK))
595 void
596 afs_GCPAGs_perproc_func(afs_proc_t * pproc)
597 {
598     afs_int32 pag, hash, uid;
599     const afs_ucred_t *pcred;
600
601     afs_GCPAGs_perproc_count++;
602
603     pcred = afs_osi_proc2cred(pproc);
604     if (!pcred)
605         return;
606
607     afs_GCPAGs_cred_count++;
608
609     pag = PagInCred(pcred);
610 #if defined(AFS_DARWIN_ENV) || defined(AFS_FBSD40_ENV) || defined(AFS_LINUX22_ENV)
611     uid = (pag != NOPAG ? pag : afs_cr_uid(pcred));
612 #elif defined(AFS_SUN510_ENV)
613     uid = (pag != NOPAG ? pag : crgetruid(pcred));
614 #else
615     uid = (pag != NOPAG ? pag : afs_cr_ruid(pcred));
616 #endif
617     hash = UHash(uid);
618
619     /* if this token is PAG based, or it's UID based and 
620      * UID-based tokens exist */
621     if ((pag != NOPAG) || (afs_GCPAGs_UIDBaseTokenCount)) {
622         /* find the entries for this uid in all cells and clear the not
623          * referenced flag.  Can't use afs_FindUser, because it just returns
624          * the specific cell asked for, or the first one found. 
625          */
626         struct unixuser *pu;
627         for (pu = afs_users[hash]; pu; pu = pu->next) {
628             if (pu->uid == uid) {
629                 if (pu->states & TMP_UPAGNotReferenced) {
630                     /* clear the 'deleteme' flag for this entry */
631                     pu->states &= ~TMP_UPAGNotReferenced;
632                     if (pag == NOPAG) {
633                         /* This is a uid based token that hadn't 
634                          * previously been cleared, so decrement the
635                          * outstanding uid based token count */
636                         afs_GCPAGs_UIDBaseTokenCount--;
637                     }
638                 }
639             }
640         }
641     }
642 }
643 #endif
644
645 /*
646  * Go through the process table, find all unused PAGs 
647  * and cause them to be deleted during the next GC.
648  *
649  * returns the number of PAGs marked for deletion
650  *
651  * On AIX we free PAGs when the last accessing process exits,
652  * so this routine is not needed.
653  *
654  * In AFS WebSecure, we explicitly call unlog when we remove
655  * an entry in the login cache, so this routine is not needed.
656  */
657
658 afs_int32
659 afs_GCPAGs(afs_int32 * ReleasedCount)
660 {
661     struct unixuser *pu;
662     int i;
663
664     if (afs_gcpags != AFS_GCPAGS_OK) {
665         return 0;
666     }
667
668     *ReleasedCount = 0;
669
670     /* first, loop through afs_users, setting the temporary 'deleteme' flag */
671     ObtainWriteLock(&afs_xuser, 419);
672     afs_GCPAGs_UIDBaseTokenCount = 0;
673     for (i = 0; i < NUSERS; i++) {
674         for (pu = afs_users[i]; pu; pu = pu->next) {
675             pu->states |= TMP_UPAGNotReferenced;
676             if (((pu->uid >> 24) & 0xff) != 'A') {
677                 /* this is a uid-based token, */
678                 /* increment the count */
679                 afs_GCPAGs_UIDBaseTokenCount++;
680             }
681         }
682     }
683
684     /* Now, iterate through the systems process table, 
685      * for each process, mark it's PAGs (if any) in use.
686      * i.e. clear the temporary deleteme flag.
687      */
688     afs_GCPAGs_perproc_count = 0;
689     afs_GCPAGs_cred_count = 0;
690
691     afs_osi_TraverseProcTable();
692
693     /* If there is an internal problem and afs_GCPAGs_perproc_func()
694      * does not get called, disable gcpags so that we do not
695      * accidentally expire all the tokens in the system.
696      */
697     if (afs_gcpags == AFS_GCPAGS_OK && !afs_GCPAGs_perproc_count) {
698         afs_gcpags = AFS_GCPAGS_EPROCWALK;
699     }
700
701     if (afs_gcpags == AFS_GCPAGS_OK && !afs_GCPAGs_cred_count) {
702         afs_gcpags = AFS_GCPAGS_ECREDWALK;
703     }
704
705     /* Now, go through afs_users again, any that aren't in use
706      * (temp deleteme flag still set) will be marked for later deletion,
707      * by setting their expire times to 0.
708      */
709     for (i = 0; i < NUSERS; i++) {
710         for (pu = afs_users[i]; pu; pu = pu->next) {
711             if (pu->states & TMP_UPAGNotReferenced) {
712
713                 /* clear the temp flag */
714                 pu->states &= ~TMP_UPAGNotReferenced;
715
716                 /* Is this entry on behalf of a 'remote' user ?
717                  * i.e. nfs translator, etc.
718                  */
719                 if (!pu->exporter && afs_gcpags == AFS_GCPAGS_OK) {
720                     /* set the expire times to 0, causes 
721                      * afs_GCUserData to remove this entry 
722                      */
723                     pu->ct.EndTimestamp = 0;
724                     pu->tokenTime = 0;
725
726                     (*ReleasedCount)++; /* remember how many we marked (info only) */
727                 }
728             }
729         }
730     }
731
732     ReleaseWriteLock(&afs_xuser);
733
734     return 0;
735 }
736
737 #endif /* AFS_GCPAGS */