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