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