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