Don't attempt to purge Citadel SYStem users.
[citadel.git] / citadel / modules / expire / serv_expire.c
1 /*
2  * $Id$
3  *
4  * This module handles the expiry of old messages and the purging of old users.
5  *
6  */
7
8
9 /*
10  * A brief technical discussion:
11  *
12  * Several of the purge operations found in this module operate in two
13  * stages: the first stage generates a linked list of objects to be deleted,
14  * then the second stage deletes all listed objects from the database.
15  *
16  * At first glance this may seem cumbersome and unnecessary.  The reason it is
17  * implemented in this way is because Berkeley DB, and possibly other backends
18  * we may hook into in the future, explicitly do _not_ support the deletion of
19  * records from a file while the file is being traversed.  The delete operation
20  * will succeed, but the traversal is not guaranteed to visit every object if
21  * this is done.  Therefore we utilize the two-stage purge.
22  *
23  * When using Berkeley DB, there's another reason for the two-phase purge: we
24  * don't want the entire thing being done as one huge transaction.
25  *
26  * You'll also notice that we build the in-memory list of records to be deleted
27  * sometimes with a linked list and sometimes with a hash table.  There is no
28  * reason for this aside from the fact that the linked list ones were written
29  * before we had the hash table library available.
30  */
31
32
33 #include "sysdep.h"
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <stdio.h>
37 #include <fcntl.h>
38 #include <signal.h>
39 #include <pwd.h>
40 #include <errno.h>
41 #include <sys/types.h>
42
43 #if TIME_WITH_SYS_TIME
44 # include <sys/time.h>
45 # include <time.h>
46 #else
47 # if HAVE_SYS_TIME_H
48 #  include <sys/time.h>
49 # else
50 #  include <time.h>
51 # endif
52 #endif
53
54 #include <sys/wait.h>
55 #include <string.h>
56 #include <limits.h>
57 #include <libcitadel.h>
58 #include "citadel.h"
59 #include "server.h"
60 #include "citserver.h"
61 #include "support.h"
62 #include "config.h"
63 #include "room_ops.h"
64 #include "policy.h"
65 #include "database.h"
66 #include "msgbase.h"
67 #include "user_ops.h"
68 #include "control.h"
69 #include "serv_network.h"       /* Needed for defenition of UseTable */
70 #include "threads.h"
71
72 #include "ctdl_module.h"
73
74
75 struct PurgeList {
76         struct PurgeList *next;
77         char name[ROOMNAMELEN]; /* use the larger of username or roomname */
78 };
79
80 struct VPurgeList {
81         struct VPurgeList *next;
82         long vp_roomnum;
83         long vp_roomgen;
84         long vp_usernum;
85 };
86
87 struct ValidRoom {
88         struct ValidRoom *next;
89         long vr_roomnum;
90         long vr_roomgen;
91 };
92
93 struct ValidUser {
94         struct ValidUser *next;
95         long vu_usernum;
96 };
97
98
99 struct ctdlroomref {
100         struct ctdlroomref *next;
101         long msgnum;
102 };
103
104 struct UPurgeList {
105         struct UPurgeList *next;
106         char up_key[256];
107 };
108
109 struct EPurgeList {
110         struct EPurgeList *next;
111         int ep_keylen;
112         char *ep_key;
113 };
114
115
116 struct PurgeList *UserPurgeList = NULL;
117 struct PurgeList *RoomPurgeList = NULL;
118 struct ValidRoom *ValidRoomList = NULL;
119 struct ValidUser *ValidUserList = NULL;
120 int messages_purged;
121 int users_not_purged;
122 char *users_corrupt_msg = NULL;
123 char *users_zero_msg = NULL;
124 struct ctdlroomref *rr = NULL;
125 extern struct CitContext *ContextList;
126 int force_purge_now = 0;                        /* set to nonzero to force a run right now */
127
128
129 /*
130  * First phase of message purge -- gather the locations of messages which
131  * qualify for purging and write them to a temp file.
132  */
133 void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
134         struct ExpirePolicy epbuf;
135         long delnum;
136         time_t xtime, now;
137         struct CtdlMessage *msg = NULL;
138         int a;
139         struct cdbdata *cdbfr;
140         long *msglist = NULL;
141         int num_msgs = 0;
142         FILE *purgelist;
143
144         purgelist = (FILE *)data;
145         fprintf(purgelist, "r=%s\n", qrbuf->QRname);
146
147         time(&now);
148         GetExpirePolicy(&epbuf, qrbuf);
149
150         /* If the room is set to never expire messages ... do nothing */
151         if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
152         if (epbuf.expire_mode == EXPIRE_MANUAL) return;
153
154         /* Don't purge messages containing system configuration, dumbass. */
155         if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
156
157         /* Ok, we got this far ... now let's see what's in the room */
158         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
159
160         if (cdbfr != NULL) {
161                 msglist = malloc(cdbfr->len);
162                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
163                 num_msgs = cdbfr->len / sizeof(long);
164                 cdb_free(cdbfr);
165         }
166
167         /* Nothing to do if there aren't any messages */
168         if (num_msgs == 0) {
169                 if (msglist != NULL) free(msglist);
170                 return;
171         }
172
173
174         /* If the room is set to expire by count, do that */
175         if (epbuf.expire_mode == EXPIRE_NUMMSGS) {
176                 if (num_msgs > epbuf.expire_value) {
177                         for (a=0; a<(num_msgs - epbuf.expire_value); ++a) {
178                                 fprintf(purgelist, "m=%ld\n", msglist[a]);
179                                 ++messages_purged;
180                         }
181                 }
182         }
183
184         /* If the room is set to expire by age... */
185         if (epbuf.expire_mode == EXPIRE_AGE) {
186                 for (a=0; a<num_msgs; ++a) {
187                         delnum = msglist[a];
188
189                         msg = CtdlFetchMessage(delnum, 0); /* dont need body */
190                         if (msg != NULL) {
191                                 xtime = atol(msg->cm_fields['T']);
192                                 CtdlFreeMessage(msg);
193                         } else {
194                                 xtime = 0L;
195                         }
196
197                         if ((xtime > 0L)
198                            && (now - xtime > (time_t)(epbuf.expire_value * 86400L))) {
199                                 fprintf(purgelist, "m=%ld\n", delnum);
200                                 ++messages_purged;
201                         }
202                 }
203         }
204
205         if (msglist != NULL) free(msglist);
206 }
207
208
209 /*
210  * Second phase of message purge -- read list of msgs from temp file and
211  * delete them.
212  */
213 void DoPurgeMessages(FILE *purgelist) {
214         char roomname[ROOMNAMELEN];
215         long msgnum;
216         char buf[SIZ];
217
218         rewind(purgelist);
219         strcpy(roomname, "nonexistent room ___ ___");
220         while (fgets(buf, sizeof buf, purgelist) != NULL) {
221                 buf[strlen(buf)-1]=0;
222                 if (!strncasecmp(buf, "r=", 2)) {
223                         strcpy(roomname, &buf[2]);
224                 }
225                 if (!strncasecmp(buf, "m=", 2)) {
226                         msgnum = atol(&buf[2]);
227                         if (msgnum > 0L) {
228                                 CtdlDeleteMessages(roomname, &msgnum, 1, "");
229                         }
230                 }
231         }
232 }
233
234
235 void PurgeMessages(void) {
236         FILE *purgelist;
237
238         CtdlLogPrintf(CTDL_DEBUG, "PurgeMessages() called\n");
239         messages_purged = 0;
240
241         purgelist = tmpfile();
242         if (purgelist == NULL) {
243                 CtdlLogPrintf(CTDL_CRIT, "Can't create purgelist temp file: %s\n",
244                         strerror(errno));
245                 return;
246         }
247
248         ForEachRoom(GatherPurgeMessages, (void *)purgelist );
249         DoPurgeMessages(purgelist);
250         fclose(purgelist);
251 }
252
253
254 void AddValidUser(struct ctdluser *usbuf, void *data) {
255         struct ValidUser *vuptr;
256
257         vuptr = (struct ValidUser *)malloc(sizeof(struct ValidUser));
258         vuptr->next = ValidUserList;
259         vuptr->vu_usernum = usbuf->usernum;
260         ValidUserList = vuptr;
261 }
262
263 void AddValidRoom(struct ctdlroom *qrbuf, void *data) {
264         struct ValidRoom *vrptr;
265
266         vrptr = (struct ValidRoom *)malloc(sizeof(struct ValidRoom));
267         vrptr->next = ValidRoomList;
268         vrptr->vr_roomnum = qrbuf->QRnumber;
269         vrptr->vr_roomgen = qrbuf->QRgen;
270         ValidRoomList = vrptr;
271 }
272
273 void DoPurgeRooms(struct ctdlroom *qrbuf, void *data) {
274         time_t age, purge_secs;
275         struct PurgeList *pptr;
276         struct ValidUser *vuptr;
277         int do_purge = 0;
278
279         /* For mailbox rooms, there's only one purging rule: if the user who
280          * owns the room still exists, we keep the room; otherwise, we purge
281          * it.  Bypass any other rules.
282          */
283         if (qrbuf->QRflags & QR_MAILBOX) {
284                 /* if user not found, do_purge will be 1 */
285                 do_purge = 1;
286                 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
287                         if (vuptr->vu_usernum == atol(qrbuf->QRname)) {
288                                 do_purge = 0;
289                         }
290                 }
291         }
292         else {
293                 /* Any of these attributes render a room non-purgable */
294                 if (qrbuf->QRflags & QR_PERMANENT) return;
295                 if (qrbuf->QRflags & QR_DIRECTORY) return;
296                 if (qrbuf->QRflags & QR_NETWORK) return;
297                 if (qrbuf->QRflags2 & QR2_SYSTEM) return;
298                 if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
299                 if (is_noneditable(qrbuf)) return;
300
301                 /* If we don't know the modification date, be safe and don't purge */
302                 if (qrbuf->QRmtime <= (time_t)0) return;
303
304                 /* If no room purge time is set, be safe and don't purge */
305                 if (config.c_roompurge < 0) return;
306
307                 /* Otherwise, check the date of last modification */
308                 age = time(NULL) - (qrbuf->QRmtime);
309                 purge_secs = (time_t)config.c_roompurge * (time_t)86400;
310                 if (purge_secs <= (time_t)0) return;
311                 CtdlLogPrintf(CTDL_DEBUG, "<%s> is <%ld> seconds old\n", qrbuf->QRname, (long)age);
312                 if (age > purge_secs) do_purge = 1;
313         } /* !QR_MAILBOX */
314
315         if (do_purge) {
316                 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
317                 pptr->next = RoomPurgeList;
318                 strcpy(pptr->name, qrbuf->QRname);
319                 RoomPurgeList = pptr;
320         }
321
322 }
323
324
325
326 int PurgeRooms(void) {
327         struct PurgeList *pptr;
328         int num_rooms_purged = 0;
329         struct ctdlroom qrbuf;
330         struct ValidUser *vuptr;
331         char *transcript = NULL;
332
333         CtdlLogPrintf(CTDL_DEBUG, "PurgeRooms() called\n");
334
335
336         /* Load up a table full of valid user numbers so we can delete
337          * user-owned rooms for users who no longer exist */
338         ForEachUser(AddValidUser, NULL);
339
340         /* Then cycle through the room file */
341         ForEachRoom(DoPurgeRooms, NULL);
342
343         /* Free the valid user list */
344         while (ValidUserList != NULL) {
345                 vuptr = ValidUserList->next;
346                 free(ValidUserList);
347                 ValidUserList = vuptr;
348         }
349
350
351         transcript = malloc(SIZ);
352         strcpy(transcript, "The following rooms have been auto-purged:\n");
353
354         while (RoomPurgeList != NULL) {
355                 if (getroom(&qrbuf, RoomPurgeList->name) == 0) {
356                         transcript=realloc(transcript, strlen(transcript)+SIZ);
357                         snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
358                                 qrbuf.QRname);
359                         delete_room(&qrbuf);
360                 }
361                 pptr = RoomPurgeList->next;
362                 free(RoomPurgeList);
363                 RoomPurgeList = pptr;
364                 ++num_rooms_purged;
365         }
366
367         if (num_rooms_purged > 0) aide_message(transcript, "Room Autopurger Message");
368         free(transcript);
369
370         CtdlLogPrintf(CTDL_DEBUG, "Purged %d rooms.\n", num_rooms_purged);
371         return(num_rooms_purged);
372 }
373
374
375 /*
376  * Back end function to check user accounts for associated Unix accounts
377  * which no longer exist.  (Only relevant for host auth mode.)
378  */
379 void do_uid_user_purge(struct ctdluser *us, void *data) {
380         struct PurgeList *pptr;
381
382         if ((us->uid != (-1)) && (us->uid != CTDLUID)) {
383                 if (getpwuid(us->uid) == NULL) {
384                         pptr = (struct PurgeList *)
385                                 malloc(sizeof(struct PurgeList));
386                         pptr->next = UserPurgeList;
387                         strcpy(pptr->name, us->fullname);
388                         UserPurgeList = pptr;
389                 }
390         }
391         else {
392                 ++users_not_purged;
393         }
394 }
395
396
397
398
399 /*
400  * Back end function to check user accounts for expiration.
401  */
402 void do_user_purge(struct ctdluser *us, void *data) {
403         int purge;
404         time_t now;
405         time_t purge_time;
406         struct PurgeList *pptr;
407
408         /* Set purge time; if the user overrides the system default, use it */
409         if (us->USuserpurge > 0) {
410                 purge_time = ((time_t)us->USuserpurge) * 86400L;
411         }
412         else {
413                 purge_time = ((time_t)config.c_userpurge) * 86400L;
414         }
415
416         /* The default rule is to not purge. */
417         purge = 0;
418         
419         /* don't attempt to purge system users. */
420         if (!strncmp(us->fullname, "SYS_", 4))
421                 goto skip_all_this;
422
423         /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account
424          * has expired, so purge the record.
425          */
426         if (config.c_userpurge > 0)
427         {
428                 now = time(NULL);
429                 if ((now - us->lastcall) > purge_time) purge = 1;
430         }
431
432         /* If the record is marked as permanent, don't purge it.
433          */
434         if (us->flags & US_PERM) purge = 0;
435
436         /* If the user is an Aide, don't purge him/her/it.
437          */
438         if (us->axlevel == 6) purge = 0;
439
440         /* If the access level is 0, the record should already have been
441          * deleted, but maybe the user was logged in at the time or something.
442          * Delete the record now.
443          */
444         if (us->axlevel == 0) purge = 1;
445
446         /* If the user set his/her password to 'deleteme', he/she
447          * wishes to be deleted, so purge the record.
448          * Moved this lower down so that aides and permanent users get purged if they ask to be.
449          */
450         if (!strcasecmp(us->password, "deleteme")) purge = 1;
451         
452         /* 0 calls is impossible.  If there are 0 calls, it must
453          * be a corrupted record, so purge it.
454          * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW)
455          */
456         if (us->timescalled < 0) purge = 1;
457
458         /* any negative user number, is
459          * also impossible.
460          */
461         if (us->usernum < 0L) purge = 1;
462         
463         /** Don't purge user 0. That user is there for the system */
464         if (us->usernum == 0L)
465         {
466                 /* FIXME: Temporary log message. Until we do unauth access with user 0 we should
467                  * try to get rid of all user 0 occurences. Many will be remnants from old code so
468                  * we will need to try and purge them from users data bases.Some will not have names but
469                  * those with names should be purged.
470                  */
471                 CtdlLogPrintf(CTDL_DEBUG, "Auto purger found a user 0 with name \"%s\"\n", us->fullname);
472                 // purge = 0;
473         }
474         
475         /* If the user has no full name entry then we can't purge them
476          * since the actual purge can't find them.
477          * This shouldn't happen but does somehow.
478          */
479         if (IsEmptyStr(us->fullname))
480         {
481                 purge = 0;
482                 
483                 if (us->usernum > 0L)
484                 {
485                         purge=0;
486                         if (users_corrupt_msg == NULL)
487                         {
488                                 users_corrupt_msg = malloc(SIZ);
489                                 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
490                                 "The system has no way to purge user with no name and should not be able to\n"
491                                 "create them either.\n"
492                                 "This indicates corruption of the user DB or possibly a bug.\n"
493                                 "It may be a good idea to restore your DB from a backup.\n");
494                         }
495                 
496                         users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
497                         snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum);
498                 }
499         }
500
501 skip_all_this:
502                 
503         if (purge == 1) {
504                 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
505                 pptr->next = UserPurgeList;
506                 strcpy(pptr->name, us->fullname);
507                 UserPurgeList = pptr;
508         }
509         else {
510                 ++users_not_purged;
511         }
512
513 }
514
515
516
517 int PurgeUsers(void) {
518         struct PurgeList *pptr;
519         int num_users_purged = 0;
520         char *transcript = NULL;
521
522         CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
523         users_not_purged = 0;
524
525         switch(config.c_auth_mode) {
526                 case AUTHMODE_NATIVE:
527                         ForEachUser(do_user_purge, NULL);
528                         break;
529                 case AUTHMODE_HOST:
530                         ForEachUser(do_uid_user_purge, NULL);
531                         break;
532                 default:
533                         CtdlLogPrintf(CTDL_DEBUG, "Unknown authentication mode!\n");
534                         break;
535         }
536
537         transcript = malloc(SIZ);
538
539         if (users_not_purged == 0) {
540                 strcpy(transcript, "The auto-purger was told to purge every user.  It is\n"
541                                 "refusing to do this because it usually indicates a problem\n"
542                                 "such as an inability to communicate with a name service.\n"
543                 );
544                 while (UserPurgeList != NULL) {
545                         pptr = UserPurgeList->next;
546                         free(UserPurgeList);
547                         UserPurgeList = pptr;
548                         ++num_users_purged;
549                 }
550         }
551
552         else {
553                 strcpy(transcript, "The following users have been auto-purged:\n");
554                 while (UserPurgeList != NULL) {
555                         transcript=realloc(transcript, strlen(transcript)+SIZ);
556                         snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
557                                 UserPurgeList->name);
558                         purge_user(UserPurgeList->name);
559                         pptr = UserPurgeList->next;
560                         free(UserPurgeList);
561                         UserPurgeList = pptr;
562                         ++num_users_purged;
563                 }
564         }
565
566         if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
567         free(transcript);
568
569         if(users_corrupt_msg)
570         {
571                 aide_message(users_corrupt_msg, "User Corruption Message");
572                 free (users_corrupt_msg);
573                 users_corrupt_msg = NULL;
574         }
575         
576         if(users_zero_msg)
577         {
578                 aide_message(users_zero_msg, "User Zero Message");
579                 free (users_zero_msg);
580                 users_zero_msg = NULL;
581         }
582                 
583         CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
584         return(num_users_purged);
585 }
586
587
588 /*
589  * Purge visits
590  *
591  * This is a really cumbersome "garbage collection" function.  We have to
592  * delete visits which refer to rooms and/or users which no longer exist.  In
593  * order to prevent endless traversals of the room and user files, we first
594  * build linked lists of rooms and users which _do_ exist on the system, then
595  * traverse the visit file, checking each record against those two lists and
596  * purging the ones that do not have a match on _both_ lists.  (Remember, if
597  * either the room or user being referred to is no longer on the system, the
598  * record is completely useless.)
599  */
600 int PurgeVisits(void) {
601         struct cdbdata *cdbvisit;
602         struct visit vbuf;
603         struct VPurgeList *VisitPurgeList = NULL;
604         struct VPurgeList *vptr;
605         int purged = 0;
606         char IndexBuf[32];
607         int IndexLen;
608         struct ValidRoom *vrptr;
609         struct ValidUser *vuptr;
610         int RoomIsValid, UserIsValid;
611
612         /* First, load up a table full of valid room/gen combinations */
613         ForEachRoom(AddValidRoom, NULL);
614
615         /* Then load up a table full of valid user numbers */
616         ForEachUser(AddValidUser, NULL);
617
618         /* Now traverse through the visits, purging irrelevant records... */
619         cdb_rewind(CDB_VISIT);
620         while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
621                 memset(&vbuf, 0, sizeof(struct visit));
622                 memcpy(&vbuf, cdbvisit->ptr,
623                         ( (cdbvisit->len > sizeof(struct visit)) ?
624                         sizeof(struct visit) : cdbvisit->len) );
625                 cdb_free(cdbvisit);
626
627                 RoomIsValid = 0;
628                 UserIsValid = 0;
629
630                 /* Check to see if the room exists */
631                 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
632                         if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
633                              && (vrptr->vr_roomgen==vbuf.v_roomgen))
634                                 RoomIsValid = 1;
635                 }
636
637                 /* Check to see if the user exists */
638                 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
639                         if (vuptr->vu_usernum == vbuf.v_usernum)
640                                 UserIsValid = 1;
641                 }
642
643                 /* Put the record on the purge list if it's dead */
644                 if ((RoomIsValid==0) || (UserIsValid==0)) {
645                         vptr = (struct VPurgeList *)
646                                 malloc(sizeof(struct VPurgeList));
647                         vptr->next = VisitPurgeList;
648                         vptr->vp_roomnum = vbuf.v_roomnum;
649                         vptr->vp_roomgen = vbuf.v_roomgen;
650                         vptr->vp_usernum = vbuf.v_usernum;
651                         VisitPurgeList = vptr;
652                 }
653
654         }
655
656         /* Free the valid room/gen combination list */
657         while (ValidRoomList != NULL) {
658                 vrptr = ValidRoomList->next;
659                 free(ValidRoomList);
660                 ValidRoomList = vrptr;
661         }
662
663         /* Free the valid user list */
664         while (ValidUserList != NULL) {
665                 vuptr = ValidUserList->next;
666                 free(ValidUserList);
667                 ValidUserList = vuptr;
668         }
669
670         /* Now delete every visit on the purged list */
671         while (VisitPurgeList != NULL) {
672                 IndexLen = GenerateRelationshipIndex(IndexBuf,
673                                 VisitPurgeList->vp_roomnum,
674                                 VisitPurgeList->vp_roomgen,
675                                 VisitPurgeList->vp_usernum);
676                 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
677                 vptr = VisitPurgeList->next;
678                 free(VisitPurgeList);
679                 VisitPurgeList = vptr;
680                 ++purged;
681         }
682
683         return(purged);
684 }
685
686 /*
687  * Purge the use table of old entries.
688  *
689  */
690 int PurgeUseTable(void) {
691         int purged = 0;
692         struct cdbdata *cdbut;
693         struct UseTable ut;
694         struct UPurgeList *ul = NULL;
695         struct UPurgeList *uptr; 
696
697         /* Phase 1: traverse through the table, discovering old records... */
698         CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
699         cdb_rewind(CDB_USETABLE);
700         while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
701
702         /*
703          * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
704          * this will release this file from the serv_network.h
705          * Maybe it could be a macro that extracts and casts the reult
706          */
707                 memcpy(&ut, cdbut->ptr,
708                        ((cdbut->len > sizeof(struct UseTable)) ?
709                         sizeof(struct UseTable) : cdbut->len));
710                 cdb_free(cdbut);
711
712                 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
713                         uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
714                         if (uptr != NULL) {
715                                 uptr->next = ul;
716                                 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
717                                 ul = uptr;
718                         }
719                         ++purged;
720                 }
721
722         }
723
724         /* Phase 2: delete the records */
725         CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
726         while (ul != NULL) {
727                 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
728                 uptr = ul->next;
729                 free(ul);
730                 ul = uptr;
731         }
732
733         CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
734         return(purged);
735 }
736
737
738
739 /*
740  * Purge the EUID Index of old records.
741  *
742  */
743 int PurgeEuidIndexTable(void) {
744         int purged = 0;
745         struct cdbdata *cdbei;
746         struct EPurgeList *el = NULL;
747         struct EPurgeList *eptr; 
748         long msgnum;
749         struct CtdlMessage *msg = NULL;
750
751         /* Phase 1: traverse through the table, discovering old records... */
752         CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
753         cdb_rewind(CDB_EUIDINDEX);
754         while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
755
756                 memcpy(&msgnum, cdbei->ptr, sizeof(long));
757
758                 msg = CtdlFetchMessage(msgnum, 0);
759                 if (msg != NULL) {
760                         CtdlFreeMessage(msg);   /* it still exists, so do nothing */
761                 }
762                 else {
763                         eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
764                         if (eptr != NULL) {
765                                 eptr->next = el;
766                                 eptr->ep_keylen = cdbei->len - sizeof(long);
767                                 eptr->ep_key = malloc(cdbei->len);
768                                 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
769                                 el = eptr;
770                         }
771                         ++purged;
772                 }
773
774                 cdb_free(cdbei);
775
776         }
777
778         /* Phase 2: delete the records */
779         CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
780         while (el != NULL) {
781                 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
782                 free(el->ep_key);
783                 eptr = el->next;
784                 free(el);
785                 el = eptr;
786         }
787
788         CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
789         return(purged);
790 }
791
792
793
794 /*
795  * Purge OpenID assocations for missing users (theoretically this will never delete anything)
796  */
797 int PurgeStaleOpenIDassociations(void) {
798         struct cdbdata *cdboi;
799         struct ctdluser usbuf;
800         HashList *keys = NULL;
801         HashPos *HashPos;
802         char *deleteme = NULL;
803         long len;
804         void *Value;
805         char *Key;
806         int num_deleted = 0;
807
808         keys = NewHash(1, NULL);
809         if (!keys) return(0);
810
811
812         cdb_rewind(CDB_OPENID);
813         while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
814                 if (cdboi->len > sizeof(long)) {
815                         long usernum;
816                         usernum = ((long)*(cdboi->ptr));
817                         if (getuserbynumber(&usbuf, usernum) != 0) {
818                                 deleteme = strdup(cdboi->ptr + sizeof(long)),
819                                 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
820                         }
821                 }
822                 cdb_free(cdboi);
823         }
824
825         /* Go through the hash list, deleting keys we stored in it */
826
827         HashPos = GetNewHashPos();
828         while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
829         {
830                 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
831                 cdb_delete(CDB_OPENID, Value, strlen(Value));
832                 /* note: don't free(Value) -- deleting the hash list will handle this for us */
833                 ++num_deleted;
834         }
835         DeleteHashPos(&HashPos);
836         DeleteHash(&keys);
837         return num_deleted;
838 }
839
840
841
842
843
844 void *purge_databases(void *args)
845 {
846         int retval;
847         static time_t last_purge = 0;
848         time_t now;
849         struct tm tm;
850         struct CitContext purgerCC;
851
852         CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
853
854         CtdlFillSystemContext(&purgerCC, "purger");
855         citthread_setspecific(MyConKey, (void *)&purgerCC );
856
857         while (!CtdlThreadCheckStop()) {
858                 /* Do the auto-purge if the current hour equals the purge hour,
859                  * but not if the operation has already been performed in the
860                  * last twelve hours.  This is usually enough granularity.
861                  */
862                 now = time(NULL);
863                 localtime_r(&now, &tm);
864                 if (
865                         ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200))
866                         && (force_purge_now == 0)
867                 ) {
868                         CtdlThreadSleep(60);
869                         continue;
870                 }
871
872
873                 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
874
875                 if (!CtdlThreadCheckStop())
876                 {
877                         retval = PurgeUsers();
878                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
879                 }
880                 
881                 if (!CtdlThreadCheckStop())
882                 {
883                         PurgeMessages();
884                         CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
885                 }
886
887                 if (!CtdlThreadCheckStop())
888                 {
889                         retval = PurgeRooms();
890                         CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
891                 }
892
893                 if (!CtdlThreadCheckStop())
894                 {
895                         retval = PurgeVisits();
896                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
897                 }
898
899                 if (!CtdlThreadCheckStop())
900                 {
901                         retval = PurgeUseTable();
902                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
903                 }
904
905                 if (!CtdlThreadCheckStop())
906                 {
907                         retval = PurgeEuidIndexTable();
908                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
909                 }
910
911                 if (!CtdlThreadCheckStop())
912                 {
913                         retval = PurgeStaleOpenIDassociations();
914                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
915                 }
916
917                 if (!CtdlThreadCheckStop())
918                 {
919                         retval = TDAP_ProcessAdjRefCountQueue();
920                         CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
921                 }
922
923                 if (!CtdlThreadCheckStop())
924                 {
925                         CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
926                         last_purge = now;       /* So we don't do it again soon */
927                         force_purge_now = 0;
928                 }
929                 else
930                         CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
931
932         }
933         return NULL;
934 }
935 /*****************************************************************************/
936
937
938 void do_fsck_msg(long msgnum, void *userdata) {
939         struct ctdlroomref *ptr;
940
941         ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
942         ptr->next = rr;
943         ptr->msgnum = msgnum;
944         rr = ptr;
945 }
946
947 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
948 {
949         getroom(&CC->room, qrbuf->QRname);
950         CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
951 }
952
953 /*
954  * Check message reference counts
955  */
956 void cmd_fsck(char *argbuf) {
957         long msgnum;
958         struct cdbdata *cdbmsg;
959         struct MetaData smi;
960         struct ctdlroomref *ptr;
961         int realcount;
962
963         if (CtdlAccessCheck(ac_aide)) return;
964
965         /* Lame way of checking whether anyone else is doing this now */
966         if (rr != NULL) {
967                 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
968                 return;
969         }
970
971         cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
972
973         cprintf("\nThis could take a while.  Please be patient!\n\n");
974         cprintf("Gathering pointers...\n");
975         ForEachRoom(do_fsck_room, NULL);
976
977         get_control();
978         cprintf("Checking message base...\n");
979         for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
980
981                 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
982                 if (cdbmsg != NULL) {
983                         cdb_free(cdbmsg);
984                         cprintf("Message %7ld    ", msgnum);
985
986                         GetMetaData(&smi, msgnum);
987                         cprintf("refcount=%-2d   ", smi.meta_refcount);
988
989                         realcount = 0;
990                         for (ptr = rr; ptr != NULL; ptr = ptr->next) {
991                                 if (ptr->msgnum == msgnum) ++realcount;
992                         }
993                         cprintf("realcount=%-2d\n", realcount);
994
995                         if ( (smi.meta_refcount != realcount)
996                            || (realcount == 0) ) {
997                                 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
998                         }
999
1000                 }
1001
1002         }
1003
1004         cprintf("Freeing memory...\n");
1005         while (rr != NULL) {
1006                 ptr = rr->next;
1007                 free(rr);
1008                 rr = ptr;
1009         }
1010
1011         cprintf("Done!\n");
1012         cprintf("000\n");
1013
1014 }
1015
1016
1017 /*
1018  * Manually initiate a run of The Dreaded Auto-Purger (tm)
1019  */
1020 void cmd_tdap(char *argbuf) {
1021         if (CtdlAccessCheck(ac_aide)) return;
1022         force_purge_now = 1;
1023         cprintf("%d Manually initiating a purger run now.\n", CIT_OK);
1024 }
1025
1026
1027 /*****************************************************************************/
1028
1029 CTDL_MODULE_INIT(expire)
1030 {
1031         if (!threading)
1032         {
1033                 CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts");
1034                 CtdlRegisterProtoHook(cmd_tdap, "TDAP", "Manually initiate auto-purger");
1035         }
1036         else
1037                 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1038         /* return our Subversion id for the Log */
1039         return "$Id$";
1040 }