]> jfr.im git - irc/quakenet/newserv.git/blob - chanserv/chanserv_cleanupdb.c
CHANSERV: more checks on lastchannelID / lastuserID
[irc/quakenet/newserv.git] / chanserv / chanserv_cleanupdb.c
1 #include "chanserv.h"
2 #include "../lib/irc_string.h"
3 #include <stdio.h>
4 #include <string.h>
5
6 static void cleanupdb(void *arg);
7 static void schedulecleanup(int hooknum, void *arg);
8
9 static unsigned int cleanupdb_active;
10 static DBModuleIdentifier q9cleanupdbid;
11
12 void _init() {
13 q9cleanupdbid = dbgetid();
14
15 registerhook(HOOK_CHANSERV_DBLOADED, schedulecleanup);
16
17 if (chanservdb_ready)
18 schedulecleanup(HOOK_CHANSERV_DBLOADED, NULL);
19 }
20
21 void _fini() {
22 deleteallschedules(cleanupdb);
23 dbfreeid(q9cleanupdbid);
24 }
25
26 static void schedulecleanup(int hooknum, void *arg) {
27 /* run at 1am but only if we're more than 15m away from it, otherwise run tomorrow */
28
29 time_t t = time(NULL);
30 time_t next_run = ((t / 86400) * 86400 + 86400) + 3600;
31 if(next_run - t < 900)
32 next_run+=86400;
33
34 schedulerecurring(next_run,0,86400,cleanupdb,NULL);
35 }
36
37 __attribute__ ((format (printf, 1, 2)))
38 static void cleanuplog(char *format, ...) {
39 char buf[512];
40 va_list va;
41
42 va_start(va, format);
43 vsnprintf(buf, sizeof(buf), format, va);
44 va_end(va);
45
46 cs_log(NULL, "CLEANUPDB %s", buf);
47 chanservwallmessage("CLEANUPDB: %s", buf);
48 }
49
50 static void cleanupdb_real(DBConn *dbconn, void *arg) {
51 reguser *vrup, *srup, *founder;
52 regchanuser *rcup, *nrcup;
53 authname *anp;
54 int i,j;
55 time_t t, to_age, unused_age, maxchan_age, authhistory_age;
56 int expired = 0, unauthed = 0, chansvaped = 0, chansempty = 0;
57 chanindex *cip, *ncip;
58 regchan *rcp;
59 DBResult *pgres;
60 unsigned int themarker;
61 unsigned int id;
62
63 t = time(NULL);
64 to_age = t - (CLEANUP_ACCOUNT_INACTIVE * 3600 * 24);
65 unused_age = t - (CLEANUP_ACCOUNT_UNUSED * 3600 * 24);
66 maxchan_age = t - (CLEANUP_CHANNEL_INACTIVE * 3600 * 24);
67 authhistory_age = t - (CLEANUP_AUTHHISTORY * 3600 * 24);
68
69 themarker=nextauthnamemarker();
70
71 if (!dbconn) {
72 cleanuplog("No DB connection, aborting.");
73 goto out;
74 }
75
76 pgres=dbgetresult(dbconn);
77
78 if (!dbquerysuccessful(pgres)) {
79 cleanuplog("DB error, aborting.");
80 goto out;
81 }
82
83 while (dbfetchrow(pgres)) {
84 id=strtoul(dbgetvalue(pgres, 0), NULL, 10);
85 anp=findauthname(id);
86 if (anp)
87 anp->marker=themarker;
88 }
89
90 dbclear(pgres);
91
92 cleanuplog("Phase 1 complete, starting phase 2 (regusers scan)...");
93
94 for (i=0;i<REGUSERHASHSIZE;i++) {
95 for (vrup=regusernicktable[i]; vrup; vrup=srup) {
96 srup=vrup->nextbyname;
97 if (!(anp=findauthname(vrup->ID)))
98 continue; /* should maybe raise hell instead */
99
100 /* If this user has the right marker, this means the authtracker data
101 * indicates that they have been active recently */
102 if (anp->marker == themarker)
103 continue;
104
105 /* HACK: don't ever delete the last user -- prevents userids being reused */
106 if (vrup->ID == lastuserID)
107 continue;
108
109 if(!anp->nicks && !UHasStaffPriv(vrup) && !UIsCleanupExempt(vrup)) {
110 if(vrup->lastauth && (vrup->lastauth < to_age)) {
111 expired++;
112 cs_log(NULL, "CLEANUPDB inactive user %s %u", vrup->username, vrup->ID);
113 } else if(!vrup->lastauth && (vrup->created < unused_age)) {
114 unauthed++;
115 cs_log(NULL, "CLEANUPDB unused user %s %u", vrup->username, vrup->ID);
116 } else {
117 continue;
118 }
119
120 cs_removeuser(vrup);
121 }
122 }
123 }
124
125 cleanuplog("Phase 2 complete, starting phase 3 (chanindex scan)...");
126
127 for (i=0;i<CHANNELHASHSIZE;i++) {
128 for (cip=chantable[i];cip;cip=ncip) {
129 ncip=cip->next;
130 if (!(rcp=cip->exts[chanservext]))
131 continue;
132
133 /* HACK: don't ever delete the last channel -- prevents channelids being reused */
134 if (rcp->ID == lastchannelID)
135 continue;
136
137 /* there's a bug here... if no joins or modes are done within the threshold
138 * and someone leaves just before the cleanup then the channel will be nuked.
139 */
140
141 /* this is one possible soln but relies on cleanupdb being run more frequently than
142 * the threshold:
143 */
144 /* slug: no longer required as we scan the entire network every 1h (cs_hourlyfunc) */
145 /*
146 if(cip->channel && cs_ischannelactive(cip->channel, rcp)) {
147 rcp->lastactive = t;
148 if (rcp->lastcountersync < (t - COUNTERSYNCINTERVAL)) {
149 csdb_updatechannelcounters(rcp);
150 rcp->lastcountersync=t;
151 }
152 }
153 */
154
155 if(rcp->lastactive < maxchan_age) {
156 /* don't remove channels with the original founder as an oper */
157 founder=findreguserbyID(rcp->founder);
158 if(founder && UHasOperPriv(founder))
159 continue;
160
161 cs_log(NULL, "CLEANUPDB inactive channel %s", cip->name?cip->name->content:"??");
162 cs_removechannel(rcp, "Channel deleted due to lack of activity.");
163 chansvaped++;
164 }
165
166 /* Get rid of any dead chanlev entries */
167 for (j=0;j<REGCHANUSERHASHSIZE;j++) {
168 for (rcup=rcp->regusers[j];rcup;rcup=nrcup) {
169 nrcup=rcup->nextbychan;
170
171 if (!rcup->flags) {
172 cs_log(NULL, "Removing user %s from channel %s (no flags)",rcup->user->username,rcp->index->name->content);
173 csdb_deletechanuser(rcup);
174 delreguserfromchannel(rcp, rcup->user);
175 freeregchanuser(rcup);
176 }
177 }
178
179 if (cs_removechannelifempty(NULL, rcp)) {
180 /* logged+parted by cs_removechannelifempty */
181 chansempty++;
182 }
183 }
184 }
185 }
186
187 cleanuplog("Phase 3 complete, starting phase 4 (history database cleanup) -- runs in the background.");
188
189 csdb_cleanuphistories(authhistory_age);
190
191 cleanuplog("Stats: %d accounts inactive for %d days, %d accounts weren't used within %d days, %d channels were inactive for %d days, %d channels empty.", expired, CLEANUP_ACCOUNT_INACTIVE, unauthed, CLEANUP_ACCOUNT_UNUSED, chansvaped, CLEANUP_CHANNEL_INACTIVE, chansempty);
192
193 out:
194 cleanupdb_active=0;
195 }
196
197 void cs_cleanupdb(nick *np) {
198 cleanupdb(np);
199 }
200
201 static void cleanupdb(void *arg) {
202 unsigned int to_age;
203 nick *np = (nick *)arg;
204
205 to_age = time(NULL) - (CLEANUP_ACCOUNT_INACTIVE * 3600 * 24);
206
207 if(np) {
208 cleanuplog("Manually started by %s.", np->nick);
209 } else {
210 cleanuplog("Automatically started.");
211 }
212
213 if (cleanupdb_active) {
214 cleanuplog("ABORTED! Cleanup already in progress! BUG BUG BUG!");
215 return;
216 }
217
218 cleanuplog("Phase 1 started (auth history data retrieval)...");
219
220 /* This query returns a single column containing the userids of all users
221 * who have active sessions now, or sessions which ended in the last
222 * CLEANUP_ACCOUNT_INACTIVE days. */
223
224 dbquery("BEGIN TRANSACTION;");
225
226 /* increase memory for aggregate (GROUP BY) -- query can take hours if this spills to disk */
227 dbquery("SET LOCAL work_mem = '512MB';");
228 q9cleanup_asyncquery(cleanupdb_real, NULL,
229 "SELECT userID from chanserv.authhistory WHERE disconnecttime=0 OR disconnecttime > %d GROUP BY userID;", to_age);
230 dbquery("COMMIT;");
231
232 cleanupdb_active=1;
233 }