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