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