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