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