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