Messages in the Local System Configuration room are now
[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
125 struct ctdlroomref *rr = NULL;
126
127 extern struct CitContext *ContextList;
128
129
130 /*
131  * First phase of message purge -- gather the locations of messages which
132  * qualify for purging and write them to a temp file.
133  */
134 void GatherPurgeMessages(struct ctdlroom *qrbuf, void *data) {
135         struct ExpirePolicy epbuf;
136         long delnum;
137         time_t xtime, now;
138         struct CtdlMessage *msg = NULL;
139         int a;
140         struct cdbdata *cdbfr;
141         long *msglist = NULL;
142         int num_msgs = 0;
143         FILE *purgelist;
144
145         purgelist = (FILE *)data;
146         fprintf(purgelist, "r=%s\n", qrbuf->QRname);
147
148         time(&now);
149         GetExpirePolicy(&epbuf, qrbuf);
150
151         /* If the room is set to never expire messages ... do nothing */
152         if (epbuf.expire_mode == EXPIRE_NEXTLEVEL) return;
153         if (epbuf.expire_mode == EXPIRE_MANUAL) return;
154
155         /* Don't purge messages containing system configuration, dumbass. */
156         if (!strcasecmp(qrbuf->QRname, SYSCONFIGROOM)) return;
157
158         /* Ok, we got this far ... now let's see what's in the room */
159         cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf->QRnumber, sizeof(long));
160
161         if (cdbfr != NULL) {
162                 msglist = malloc(cdbfr->len);
163                 memcpy(msglist, cdbfr->ptr, cdbfr->len);
164                 num_msgs = cdbfr->len / sizeof(long);
165                 cdb_free(cdbfr);
166         }
167
168         /* Nothing to do if there aren't any messages */
169         if (num_msgs == 0) {
170                 if (msglist != NULL) free(msglist);
171                 return;
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         /* If the user hasn't called in two months and expiring of accounts is turned on, his/her account
420          * has expired, so purge the record.
421          */
422         if (config.c_userpurge > 0)
423         {
424                 now = time(NULL);
425                 if ((now - us->lastcall) > purge_time) purge = 1;
426         }
427
428         /* If the record is marked as permanent, don't purge it.
429          */
430         if (us->flags & US_PERM) purge = 0;
431
432         /* If the user is an Aide, don't purge him/her/it.
433          */
434         if (us->axlevel == 6) purge = 0;
435
436         /* If the access level is 0, the record should already have been
437          * deleted, but maybe the user was logged in at the time or something.
438          * Delete the record now.
439          */
440         if (us->axlevel == 0) purge = 1;
441
442         /* If the user set his/her password to 'deleteme', he/she
443          * wishes to be deleted, so purge the record.
444          * Moved this lower down so that aides and permanent users get purged if they ask to be.
445          */
446         if (!strcasecmp(us->password, "deleteme")) purge = 1;
447         
448         /* 0 calls is impossible.  If there are 0 calls, it must
449          * be a corrupted record, so purge it.
450          * Actually it is possible if an Aide created the user so now we check for less than 0 (DRW)
451          */
452         if (us->timescalled < 0) purge = 1;
453
454         /* any negative user number, is
455          * also impossible.
456          */
457         if (us->usernum < 0L) purge = 1;
458         
459         /** Don't purge user 0. That user is there for the system */
460         if (us->usernum == 0L)
461         {
462                 /* FIXME: Temporary log message. Until we do unauth access with user 0 we should
463                  * try to get rid of all user 0 occurences. Many will be remnants from old code so
464                  * we will need to try and purge them from users data bases.Some will not have names but
465                  * those with names should be purged.
466                  */
467                 CtdlLogPrintf(CTDL_DEBUG, "Auto purger found a user 0 with name \"%s\"\n", us->fullname);
468                 // purge = 0;
469         }
470         
471         /* If the user has no full name entry then we can't purge them
472          * since the actual purge can't find them.
473          * This shouldn't happen but does somehow.
474          */
475         if (IsEmptyStr(us->fullname))
476         {
477                 purge = 0;
478                 
479                 if (us->usernum > 0L)
480                 {
481                         purge=0;
482                         if (users_corrupt_msg == NULL)
483                         {
484                                 users_corrupt_msg = malloc(SIZ);
485                                 strcpy(users_corrupt_msg, "The auto-purger found the following user numbers with no name.\n"
486                                 "The system has no way to purge user with no name and should not be able to\n"
487                                 "create them either.\n"
488                                 "This indicates corruption of the user DB or possibly a bug.\n"
489                                 "It may be a good idea to restore your DB from a backup.\n");
490                         }
491                 
492                         users_corrupt_msg=realloc(users_corrupt_msg, strlen(users_corrupt_msg)+30);
493                         snprintf(&users_corrupt_msg[strlen(users_corrupt_msg)], 29, " %ld\n", us->usernum);
494                 }
495         }
496
497
498         if (purge == 1) {
499                 pptr = (struct PurgeList *) malloc(sizeof(struct PurgeList));
500                 pptr->next = UserPurgeList;
501                 strcpy(pptr->name, us->fullname);
502                 UserPurgeList = pptr;
503         }
504         else {
505                 ++users_not_purged;
506         }
507
508 }
509
510
511
512 int PurgeUsers(void) {
513         struct PurgeList *pptr;
514         int num_users_purged = 0;
515         char *transcript = NULL;
516
517         CtdlLogPrintf(CTDL_DEBUG, "PurgeUsers() called\n");
518         users_not_purged = 0;
519
520         switch(config.c_auth_mode) {
521                 case AUTHMODE_NATIVE:
522                         ForEachUser(do_user_purge, NULL);
523                         break;
524                 case AUTHMODE_HOST:
525                         ForEachUser(do_uid_user_purge, NULL);
526                         break;
527                 default:
528                         CtdlLogPrintf(CTDL_DEBUG, "Unknown authentication mode!\n");
529                         break;
530         }
531
532         transcript = malloc(SIZ);
533
534         if (users_not_purged == 0) {
535                 strcpy(transcript, "The auto-purger was told to purge every user.  It is\n"
536                                 "refusing to do this because it usually indicates a problem\n"
537                                 "such as an inability to communicate with a name service.\n"
538                 );
539                 while (UserPurgeList != NULL) {
540                         pptr = UserPurgeList->next;
541                         free(UserPurgeList);
542                         UserPurgeList = pptr;
543                         ++num_users_purged;
544                 }
545         }
546
547         else {
548                 strcpy(transcript, "The following users have been auto-purged:\n");
549                 while (UserPurgeList != NULL) {
550                         transcript=realloc(transcript, strlen(transcript)+SIZ);
551                         snprintf(&transcript[strlen(transcript)], SIZ, " %s\n",
552                                 UserPurgeList->name);
553                         purge_user(UserPurgeList->name);
554                         pptr = UserPurgeList->next;
555                         free(UserPurgeList);
556                         UserPurgeList = pptr;
557                         ++num_users_purged;
558                 }
559         }
560
561         if (num_users_purged > 0) aide_message(transcript, "User Purge Message");
562         free(transcript);
563
564         if(users_corrupt_msg)
565         {
566                 aide_message(users_corrupt_msg, "User Corruption Message");
567                 free (users_corrupt_msg);
568                 users_corrupt_msg = NULL;
569         }
570         
571         if(users_zero_msg)
572         {
573                 aide_message(users_zero_msg, "User Zero Message");
574                 free (users_zero_msg);
575                 users_zero_msg = NULL;
576         }
577                 
578         CtdlLogPrintf(CTDL_DEBUG, "Purged %d users.\n", num_users_purged);
579         return(num_users_purged);
580 }
581
582
583 /*
584  * Purge visits
585  *
586  * This is a really cumbersome "garbage collection" function.  We have to
587  * delete visits which refer to rooms and/or users which no longer exist.  In
588  * order to prevent endless traversals of the room and user files, we first
589  * build linked lists of rooms and users which _do_ exist on the system, then
590  * traverse the visit file, checking each record against those two lists and
591  * purging the ones that do not have a match on _both_ lists.  (Remember, if
592  * either the room or user being referred to is no longer on the system, the
593  * record is completely useless.)
594  */
595 int PurgeVisits(void) {
596         struct cdbdata *cdbvisit;
597         struct visit vbuf;
598         struct VPurgeList *VisitPurgeList = NULL;
599         struct VPurgeList *vptr;
600         int purged = 0;
601         char IndexBuf[32];
602         int IndexLen;
603         struct ValidRoom *vrptr;
604         struct ValidUser *vuptr;
605         int RoomIsValid, UserIsValid;
606
607         /* First, load up a table full of valid room/gen combinations */
608         ForEachRoom(AddValidRoom, NULL);
609
610         /* Then load up a table full of valid user numbers */
611         ForEachUser(AddValidUser, NULL);
612
613         /* Now traverse through the visits, purging irrelevant records... */
614         cdb_rewind(CDB_VISIT);
615         while(cdbvisit = cdb_next_item(CDB_VISIT), cdbvisit != NULL) {
616                 memset(&vbuf, 0, sizeof(struct visit));
617                 memcpy(&vbuf, cdbvisit->ptr,
618                         ( (cdbvisit->len > sizeof(struct visit)) ?
619                         sizeof(struct visit) : cdbvisit->len) );
620                 cdb_free(cdbvisit);
621
622                 RoomIsValid = 0;
623                 UserIsValid = 0;
624
625                 /* Check to see if the room exists */
626                 for (vrptr=ValidRoomList; vrptr!=NULL; vrptr=vrptr->next) {
627                         if ( (vrptr->vr_roomnum==vbuf.v_roomnum)
628                              && (vrptr->vr_roomgen==vbuf.v_roomgen))
629                                 RoomIsValid = 1;
630                 }
631
632                 /* Check to see if the user exists */
633                 for (vuptr=ValidUserList; vuptr!=NULL; vuptr=vuptr->next) {
634                         if (vuptr->vu_usernum == vbuf.v_usernum)
635                                 UserIsValid = 1;
636                 }
637
638                 /* Put the record on the purge list if it's dead */
639                 if ((RoomIsValid==0) || (UserIsValid==0)) {
640                         vptr = (struct VPurgeList *)
641                                 malloc(sizeof(struct VPurgeList));
642                         vptr->next = VisitPurgeList;
643                         vptr->vp_roomnum = vbuf.v_roomnum;
644                         vptr->vp_roomgen = vbuf.v_roomgen;
645                         vptr->vp_usernum = vbuf.v_usernum;
646                         VisitPurgeList = vptr;
647                 }
648
649         }
650
651         /* Free the valid room/gen combination list */
652         while (ValidRoomList != NULL) {
653                 vrptr = ValidRoomList->next;
654                 free(ValidRoomList);
655                 ValidRoomList = vrptr;
656         }
657
658         /* Free the valid user list */
659         while (ValidUserList != NULL) {
660                 vuptr = ValidUserList->next;
661                 free(ValidUserList);
662                 ValidUserList = vuptr;
663         }
664
665         /* Now delete every visit on the purged list */
666         while (VisitPurgeList != NULL) {
667                 IndexLen = GenerateRelationshipIndex(IndexBuf,
668                                 VisitPurgeList->vp_roomnum,
669                                 VisitPurgeList->vp_roomgen,
670                                 VisitPurgeList->vp_usernum);
671                 cdb_delete(CDB_VISIT, IndexBuf, IndexLen);
672                 vptr = VisitPurgeList->next;
673                 free(VisitPurgeList);
674                 VisitPurgeList = vptr;
675                 ++purged;
676         }
677
678         return(purged);
679 }
680
681 /*
682  * Purge the use table of old entries.
683  *
684  */
685 int PurgeUseTable(void) {
686         int purged = 0;
687         struct cdbdata *cdbut;
688         struct UseTable ut;
689         struct UPurgeList *ul = NULL;
690         struct UPurgeList *uptr; 
691
692         /* Phase 1: traverse through the table, discovering old records... */
693         CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 1\n");
694         cdb_rewind(CDB_USETABLE);
695         while(cdbut = cdb_next_item(CDB_USETABLE), cdbut != NULL) {
696
697         /*
698          * TODODRW: change this to create a new function time_t cdb_get_timestamp( struct cdbdata *)
699          * this will release this file from the serv_network.h
700          * Maybe it could be a macro that extracts and casts the reult
701          */
702                 memcpy(&ut, cdbut->ptr,
703                        ((cdbut->len > sizeof(struct UseTable)) ?
704                         sizeof(struct UseTable) : cdbut->len));
705                 cdb_free(cdbut);
706
707                 if ( (time(NULL) - ut.ut_timestamp) > USETABLE_RETAIN ) {
708                         uptr = (struct UPurgeList *) malloc(sizeof(struct UPurgeList));
709                         if (uptr != NULL) {
710                                 uptr->next = ul;
711                                 safestrncpy(uptr->up_key, ut.ut_msgid, sizeof uptr->up_key);
712                                 ul = uptr;
713                         }
714                         ++purged;
715                 }
716
717         }
718
719         /* Phase 2: delete the records */
720         CtdlLogPrintf(CTDL_DEBUG, "Purge use table: phase 2\n");
721         while (ul != NULL) {
722                 cdb_delete(CDB_USETABLE, ul->up_key, strlen(ul->up_key));
723                 uptr = ul->next;
724                 free(ul);
725                 ul = uptr;
726         }
727
728         CtdlLogPrintf(CTDL_DEBUG, "Purge use table: finished (purged %d records)\n", purged);
729         return(purged);
730 }
731
732
733
734 /*
735  * Purge the EUID Index of old records.
736  *
737  */
738 int PurgeEuidIndexTable(void) {
739         int purged = 0;
740         struct cdbdata *cdbei;
741         struct EPurgeList *el = NULL;
742         struct EPurgeList *eptr; 
743         long msgnum;
744         struct CtdlMessage *msg = NULL;
745
746         /* Phase 1: traverse through the table, discovering old records... */
747         CtdlLogPrintf(CTDL_DEBUG, "Purge EUID index: phase 1\n");
748         cdb_rewind(CDB_EUIDINDEX);
749         while(cdbei = cdb_next_item(CDB_EUIDINDEX), cdbei != NULL) {
750
751                 memcpy(&msgnum, cdbei->ptr, sizeof(long));
752
753                 msg = CtdlFetchMessage(msgnum, 0);
754                 if (msg != NULL) {
755                         CtdlFreeMessage(msg);   /* it still exists, so do nothing */
756                 }
757                 else {
758                         eptr = (struct EPurgeList *) malloc(sizeof(struct EPurgeList));
759                         if (eptr != NULL) {
760                                 eptr->next = el;
761                                 eptr->ep_keylen = cdbei->len - sizeof(long);
762                                 eptr->ep_key = malloc(cdbei->len);
763                                 memcpy(eptr->ep_key, &cdbei->ptr[sizeof(long)], eptr->ep_keylen);
764                                 el = eptr;
765                         }
766                         ++purged;
767                 }
768
769                 cdb_free(cdbei);
770
771         }
772
773         /* Phase 2: delete the records */
774         CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: phase 2\n");
775         while (el != NULL) {
776                 cdb_delete(CDB_EUIDINDEX, el->ep_key, el->ep_keylen);
777                 free(el->ep_key);
778                 eptr = el->next;
779                 free(el);
780                 el = eptr;
781         }
782
783         CtdlLogPrintf(CTDL_DEBUG, "Purge euid index: finished (purged %d records)\n", purged);
784         return(purged);
785 }
786
787
788
789 /*
790  * Purge OpenID assocations for missing users (theoretically this will never delete anything)
791  */
792 int PurgeStaleOpenIDassociations(void) {
793         struct cdbdata *cdboi;
794         struct ctdluser usbuf;
795         HashList *keys = NULL;
796         HashPos *HashPos;
797         char *deleteme = NULL;
798         long len;
799         void *Value;
800         char *Key;
801         int num_deleted = 0;
802
803         keys = NewHash(1, NULL);
804         if (!keys) return(0);
805
806
807         cdb_rewind(CDB_OPENID);
808         while (cdboi = cdb_next_item(CDB_OPENID), cdboi != NULL) {
809                 if (cdboi->len > sizeof(long)) {
810                         long usernum;
811                         usernum = ((long)*(cdboi->ptr));
812                         if (getuserbynumber(&usbuf, usernum) != 0) {
813                                 deleteme = strdup(cdboi->ptr + sizeof(long)),
814                                 Put(keys, deleteme, strlen(deleteme), deleteme, generic_free_handler);
815                         }
816                 }
817                 cdb_free(cdboi);
818         }
819
820         /* Go through the hash list, deleting keys we stored in it */
821
822         HashPos = GetNewHashPos();
823         while (GetNextHashPos(keys, HashPos, &len, &Key, &Value)!=0)
824         {
825                 CtdlLogPrintf(CTDL_DEBUG, "Deleting associated OpenID <%s>\n", Value);
826                 cdb_delete(CDB_OPENID, Value, strlen(Value));
827                 /* note: don't free(Value) -- deleting the hash list will handle this for us */
828                 ++num_deleted;
829         }
830         DeleteHashPos(&HashPos);
831         DeleteHash(&keys);
832         return num_deleted;
833 }
834
835
836
837
838
839 void *purge_databases(void *args)
840 {
841         int retval;
842         static time_t last_purge = 0;
843         time_t now;
844         struct tm tm;
845         struct CitContext purgerCC;
846
847         CtdlLogPrintf(CTDL_DEBUG, "Auto-purger_thread() initializing\n");
848
849         CtdlFillSystemContext(&purgerCC, "purger");
850         citthread_setspecific(MyConKey, (void *)&purgerCC );
851
852         while (!CtdlThreadCheckStop()) {
853                 /* Do the auto-purge if the current hour equals the purge hour,
854                  * but not if the operation has already been performed in the
855                  * last twelve hours.  This is usually enough granularity.
856                  */
857                 now = time(NULL);
858                 localtime_r(&now, &tm);
859                 if ((tm.tm_hour != config.c_purge_hour) || ((now - last_purge) < 43200)) {
860                         CtdlThreadSleep(60);
861                         continue;
862                 }
863
864
865                 CtdlLogPrintf(CTDL_INFO, "Auto-purger: starting.\n");
866
867                 if (!CtdlThreadCheckStop())
868                 {
869                         retval = PurgeUsers();
870                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d users.\n", retval);
871                 }
872                 
873                 if (!CtdlThreadCheckStop())
874                 {
875                         PurgeMessages();
876                         CtdlLogPrintf(CTDL_NOTICE, "Expired %d messages.\n", messages_purged);
877                 }
878
879                 if (!CtdlThreadCheckStop())
880                 {
881                         retval = PurgeRooms();
882                         CtdlLogPrintf(CTDL_NOTICE, "Expired %d rooms.\n", retval);
883                 }
884
885                 if (!CtdlThreadCheckStop())
886                 {
887                         retval = PurgeVisits();
888                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d visits.\n", retval);
889                 }
890
891                 if (!CtdlThreadCheckStop())
892                 {
893                         retval = PurgeUseTable();
894                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the use table.\n", retval);
895                 }
896
897                 if (!CtdlThreadCheckStop())
898                 {
899                         retval = PurgeEuidIndexTable();
900                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d entries from the EUID index.\n", retval);
901                 }
902
903                 if (!CtdlThreadCheckStop())
904                 {
905                         retval = PurgeStaleOpenIDassociations();
906                         CtdlLogPrintf(CTDL_NOTICE, "Purged %d stale OpenID associations.\n", retval);
907                 }
908
909                 if (!CtdlThreadCheckStop())
910                 {
911                         retval = TDAP_ProcessAdjRefCountQueue();
912                         CtdlLogPrintf(CTDL_NOTICE, "Processed %d message reference count adjustments.\n", retval);
913                 }
914
915                 if (!CtdlThreadCheckStop())
916                 {
917                         CtdlLogPrintf(CTDL_INFO, "Auto-purger: finished.\n");
918                         last_purge = now;       /* So we don't do it again soon */
919                 }
920                 else
921                         CtdlLogPrintf(CTDL_INFO, "Auto-purger: STOPPED.\n");
922
923         }
924         return NULL;
925 }
926 /*****************************************************************************/
927
928
929 void do_fsck_msg(long msgnum, void *userdata) {
930         struct ctdlroomref *ptr;
931
932         ptr = (struct ctdlroomref *)malloc(sizeof(struct ctdlroomref));
933         ptr->next = rr;
934         ptr->msgnum = msgnum;
935         rr = ptr;
936 }
937
938 void do_fsck_room(struct ctdlroom *qrbuf, void *data)
939 {
940         getroom(&CC->room, qrbuf->QRname);
941         CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, do_fsck_msg, NULL);
942 }
943
944 /*
945  * Check message reference counts
946  */
947 void cmd_fsck(char *argbuf) {
948         long msgnum;
949         struct cdbdata *cdbmsg;
950         struct MetaData smi;
951         struct ctdlroomref *ptr;
952         int realcount;
953
954         if (CtdlAccessCheck(ac_aide)) return;
955
956         /* Lame way of checking whether anyone else is doing this now */
957         if (rr != NULL) {
958                 cprintf("%d Another FSCK is already running.\n", ERROR + RESOURCE_BUSY);
959                 return;
960         }
961
962         cprintf("%d Checking message reference counts\n", LISTING_FOLLOWS);
963
964         cprintf("\nThis could take a while.  Please be patient!\n\n");
965         cprintf("Gathering pointers...\n");
966         ForEachRoom(do_fsck_room, NULL);
967
968         get_control();
969         cprintf("Checking message base...\n");
970         for (msgnum = 0L; msgnum <= CitControl.MMhighest; ++msgnum) {
971
972                 cdbmsg = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
973                 if (cdbmsg != NULL) {
974                         cdb_free(cdbmsg);
975                         cprintf("Message %7ld    ", msgnum);
976
977                         GetMetaData(&smi, msgnum);
978                         cprintf("refcount=%-2d   ", smi.meta_refcount);
979
980                         realcount = 0;
981                         for (ptr = rr; ptr != NULL; ptr = ptr->next) {
982                                 if (ptr->msgnum == msgnum) ++realcount;
983                         }
984                         cprintf("realcount=%-2d\n", realcount);
985
986                         if ( (smi.meta_refcount != realcount)
987                            || (realcount == 0) ) {
988                                 AdjRefCount(msgnum, (smi.meta_refcount - realcount));
989                         }
990
991                 }
992
993         }
994
995         cprintf("Freeing memory...\n");
996         while (rr != NULL) {
997                 ptr = rr->next;
998                 free(rr);
999                 rr = ptr;
1000         }
1001
1002         cprintf("Done!\n");
1003         cprintf("000\n");
1004
1005 }
1006
1007
1008
1009
1010 /*****************************************************************************/
1011
1012 CTDL_MODULE_INIT(expire)
1013 {
1014         if (!threading)
1015         {
1016                 CtdlRegisterProtoHook(cmd_fsck, "FSCK", "Check message ref counts");
1017         }
1018         else
1019                 CtdlThreadCreate("Auto Purger", CTDLTHREAD_BIGSTACK, purge_databases, NULL);
1020         /* return our Subversion id for the Log */
1021         return "$Id$";
1022 }