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