]> jfr.im git - irc/quakenet/newserv.git/blob - nickwatch/nickwatch.c
BUILD: add require-all build mode
[irc/quakenet/newserv.git] / nickwatch / nickwatch.c
1 #include <stdarg.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include "../core/schedule.h"
5 #include "../lib/irc_string.h"
6 #include "../lib/splitline.h"
7 #include "../control/control.h"
8 #include "../newsearch/newsearch.h"
9 #include "../newsearch/parser.h"
10
11 #define NW_FORMAT_TIME "%d/%m/%y %H:%M GMT"
12 #define NW_DURATION_MAX (60*60*24*7) // 7 days
13
14 typedef struct nickwatch {
15 int id;
16
17 char createdby[64];
18 int hits;
19 time_t lastactive;
20 time_t expiry;
21 char term[512];
22 parsertree *tree;
23
24 struct nickwatch *next;
25 } nickwatch;
26
27 typedef struct nickwatchevent {
28 char description[128];
29 struct nickwatchevent *next;
30 } nickwatchevent;
31
32 static nickwatch *nickwatches;
33 static int nextnickwatch = 1;
34 static int nickwatchext;
35
36 static void nw_dummyreply(nick *np, char *format, ...) { }
37 static void nw_dummywall(int level, char *format, ...) { }
38
39 static nickwatch *nw_currentwatch;
40 static array nw_pendingnicks;
41
42 static int nw_nickunwatch(int id) {
43 nickwatch **pnext, *nw;
44
45 for (pnext = &nickwatches; *pnext; pnext = &((*pnext)->next)) {
46 nw = *pnext;
47
48 if (nw->id == id) {
49 parse_free(nw->tree);
50 *pnext = nw->next;
51 free(nw);
52 return 0;
53 }
54 }
55
56 return 1;
57 }
58
59 static void nw_printnick(searchCtx *ctx, nick *sender, nick *np) {
60 char hostbuf[HOSTLEN+NICKLEN+USERLEN+4], modebuf[34];
61 char events[512];
62 nickwatchevent *nwe = np->exts[nickwatchext];
63 int len;
64
65 nw_currentwatch->hits++;
66 nw_currentwatch->lastactive = time(NULL);
67
68 events[0] = '\0';
69 len = 0;
70
71 for (nwe = np->exts[nickwatchext]; nwe; nwe = nwe->next) {
72 if (len > 0)
73 len += snprintf(events + len, sizeof(events) - len, ", ");
74
75 len += snprintf(events + len, sizeof(events) - len, "%s", nwe->description);
76 }
77
78 strncpy(modebuf, printflags(np->umodes, umodeflags), sizeof(modebuf));
79
80 controlwall(NO_OPER, NL_HITS, "nickwatch(#%d, %s): %s [%s] (%s) (%s)", nw_currentwatch->id, events, visiblehostmask(np,hostbuf),
81 IPtostr(np->ipaddress), modebuf, np->realname->name->content);
82 }
83
84 static void nwe_enqueue(nick *np, const char *format, ...) {
85 nickwatchevent *nwe;
86 va_list va;
87 int slot;
88
89 nwe = malloc(sizeof(nickwatchevent));
90
91 va_start(va, format);
92 vsnprintf(nwe->description, sizeof(nwe->description), format, va);
93 va_end(va);
94
95 nwe->next = np->exts[nickwatchext];
96 np->exts[nickwatchext] = nwe;
97
98 slot = array_getfreeslot(&nw_pendingnicks);
99 ((nick **)nw_pendingnicks.content)[slot] = np;
100 }
101
102 static void nwe_clear(nick *np) {
103 nickwatchevent *nwe, *next;
104
105 for (nwe = np->exts[nickwatchext]; nwe; nwe = next) {
106 next = nwe->next;
107 free(nwe);
108 }
109
110 np->exts[nickwatchext] = NULL;
111 }
112
113 static void nw_sched_processevents(void *arg) {
114 nickwatch *nw, *next;
115 int i, slot;
116 unsigned int marker;
117 nick *np;
118 array nicks;
119 time_t now = time(NULL);
120
121 array_init(&nicks, sizeof(nick *));
122 marker = nextnickmarker();
123
124 for (i = 0; i < nw_pendingnicks.cursi; i++) {
125 np = ((nick **)nw_pendingnicks.content)[i];
126
127 if (!np)
128 continue;
129
130 if (np->marker != marker) {
131 np->marker = marker;
132 slot = array_getfreeslot(&nicks);
133 ((nick **)nicks.content)[slot] = np;
134 }
135 }
136
137 array_free(&nw_pendingnicks);
138 array_init(&nw_pendingnicks, sizeof(nick *));
139
140 for (nw = nickwatches; nw; nw = next) {
141 nw_currentwatch = nw;
142 next = nw->next;
143 ast_nicksearch(nw->tree->root, &nw_dummyreply, mynick, &nw_dummywall, &nw_printnick, NULL, NULL, 10, &nicks);
144 if (nw->expiry && nw->expiry <= now) {
145 controlwall(NO_OPER, NL_HITS, "nickwatch(#%d) by %s expired (%d hits): %s", nw->id, nw->createdby, nw->hits, nw->term);
146 nw_nickunwatch(nw->id);
147 }
148 }
149
150 for (i = 0; i < nicks.cursi; i++) {
151 np = ((nick **)nicks.content)[i];
152 nwe_clear(np);
153 }
154
155 array_free(&nicks);
156 }
157
158 static void nw_hook_newnick(int hooknum, void *arg) {
159 nick *np = arg;
160 nwe_enqueue(np, "new user");
161 }
162
163 static void nw_hook_account(int hooknum, void *arg) {
164 nick *np = arg;
165 nwe_enqueue(np, "logged in with account %s", np->authname);
166 }
167
168 static void nw_hook_lostnick(int hooknum, void *arg) {
169 nick *np = arg;
170 int i;
171
172 nwe_clear(np);
173
174 for (i = 0; i < nw_pendingnicks.cursi; i++)
175 if (((nick **)nw_pendingnicks.content)[i] == np)
176 ((nick **)nw_pendingnicks.content)[i] = NULL;
177 }
178
179 static void nw_hook_rename(int hooknum, void *arg) {
180 void **args = arg;
181 nick *np = args[0];
182 char *oldnick = args[1];
183 nwe_enqueue(np, "renamed from %s", oldnick);
184 }
185
186 static void nw_hook_umodechange(int hooknum, void *arg) {
187 void **args = arg;
188 nick *np = args[0];
189 flag_t oldmodes = (uintptr_t)args[1];
190 char buf[64];
191 strncpy(buf, printflags(np->umodes, umodeflags), sizeof(buf));
192 nwe_enqueue(np, "umodes %s -> %s", printflags(oldmodes, umodeflags), buf);
193 }
194
195 static void nw_hook_message(int hooknum, void *arg) {
196 void **args = arg;
197 nick *np = args[0];
198 int isnotice = (uintptr_t)args[2];
199 nwe_enqueue(np, isnotice ? "notice" : "message");
200 }
201
202 static void nw_hook_joinchannel(int hooknum, void *arg) {
203 void **args = arg;
204 channel *cp = args[0];
205 nick *np = args[1];
206 nwe_enqueue(np, "join channel %s", cp->index->name->content);
207 }
208
209 static int nw_cmd_nickwatch(void *source, int cargc, char **cargv) {
210 nick *sender = source;
211 nickwatch *nw;
212 parsertree *tree;
213 time_t duration = NW_DURATION_MAX;
214 size_t i;
215
216 for (i = 0; i < cargc && cargv[i][0] == '-'; i++) {
217 switch (cargv[i][1]) {
218 case 'd':
219 if (++i == cargc)
220 return CMD_USAGE;
221 duration = durationtolong(cargv[i]);
222 if (!duration || duration > NW_DURATION_MAX) {
223 controlreply(sender, "Invalid duration. Maximum: %s.", longtoduration(NW_DURATION_MAX, 1));
224 return CMD_ERROR;
225 }
226 break;
227 default:
228 return CMD_USAGE;
229 }
230 }
231
232 if (i == cargc)
233 return CMD_USAGE;
234
235 if (i < (cargc - 1))
236 rejoinline(cargv[i],cargc-i);
237
238 tree = parse_string(reg_nicksearch, cargv[i]);
239 if (!tree) {
240 displaystrerror(controlreply, sender, cargv[i]);
241 return CMD_ERROR;
242 }
243
244 nw = malloc(sizeof(nickwatch));
245 nw->id = nextnickwatch++;
246 snprintf(nw->createdby, sizeof(nw->createdby), "#%s", sender->authname);
247 nw->hits = 0;
248 nw->lastactive = 0;
249 nw->expiry = duration + time(NULL);
250 strncpy(nw->term, cargv[i], sizeof(nw->term));
251 nw->tree = tree;
252 nw->next = nickwatches;
253 nickwatches = nw;
254
255 controlreply(sender, "Done.");
256
257 return CMD_OK;
258 }
259
260 static int nw_cmd_nickunwatch(void *source, int cargc, char **cargv) {
261 nick *sender = source;
262 int id;
263
264 if (cargc < 1)
265 return CMD_USAGE;
266
267 id = atoi(cargv[0]);
268
269 if (nw_nickunwatch(id)) {
270 controlreply(sender, "Nickwatch #%d not found.", id);
271 return CMD_ERROR;
272 }
273
274 controlreply(sender, "Done.");
275 return CMD_OK;
276 }
277
278 static void nw_formattime(time_t time, char *buf, size_t bufsize) {
279 if (time == 0)
280 strncpy(buf, "(never)", bufsize);
281 else
282 strftime(buf, bufsize, NW_FORMAT_TIME, gmtime(&time));
283 }
284
285
286 static int nw_cmd_nickwatches(void *source, int cargc, char **cargv) {
287 nick *sender = source;
288 nickwatch *nw;
289 char timebuf1[20], timebuf2[20];
290
291 controlreply(sender, "ID Created By Hits Expires Last active Term");
292
293 for (nw = nickwatches; nw; nw = nw->next) {
294 nw_formattime(nw->expiry, timebuf1, sizeof(timebuf1));
295 nw_formattime(nw->lastactive, timebuf2, sizeof(timebuf2));
296 controlreply(sender, "%-5d %-15s %-7d %-18s %-18s %s", nw->id, nw->createdby, nw->hits, timebuf1, timebuf2, nw->term);
297 }
298
299 controlreply(sender, "--- End of nickwatches.");
300
301 return CMD_OK;
302 }
303
304 void _init(void) {
305 nickwatchext = registernickext("nickwatch");
306
307 array_init(&nw_pendingnicks, sizeof(nick *));
308
309 registercontrolhelpcmd("nickwatch", NO_OPER, 3, &nw_cmd_nickwatch, "Usage: nickwatch ?-d <duration (e.g. 12h5m)>? <nicksearch term>\nAdds a nickwatch entry.");
310 registercontrolhelpcmd("nickunwatch", NO_OPER, 1, &nw_cmd_nickunwatch, "Usage: nickunwatch <#id>\nRemoves a nickwatch entry.");
311 registercontrolhelpcmd("nickwatches", NO_OPER, 0, &nw_cmd_nickwatches, "Usage: nickwatches\nLists nickwatches.");
312
313 registerhook(HOOK_NICK_NEWNICK, &nw_hook_newnick);
314 registerhook(HOOK_NICK_ACCOUNT, &nw_hook_account);
315 registerhook(HOOK_NICK_LOSTNICK, &nw_hook_lostnick);
316 registerhook(HOOK_NICK_RENAME, &nw_hook_rename);
317 registerhook(HOOK_NICK_MODECHANGE, &nw_hook_umodechange);
318 registerhook(HOOK_NICK_MESSAGE, &nw_hook_message);
319 registerhook(HOOK_CHANNEL_CREATE, &nw_hook_joinchannel);
320 registerhook(HOOK_CHANNEL_JOIN, &nw_hook_joinchannel);
321
322 schedulerecurring(time(NULL) + 5, 0, 1, &nw_sched_processevents, NULL);
323 }
324
325 void _fini(void) {
326 nickwatch *nw, *next;
327
328 deregistercontrolcmd("nickwatch", &nw_cmd_nickwatch);
329 deregistercontrolcmd("nickunwatch", &nw_cmd_nickunwatch);
330 deregistercontrolcmd("nickwatches", &nw_cmd_nickwatches);
331
332 deregisterhook(HOOK_NICK_NEWNICK, &nw_hook_newnick);
333 deregisterhook(HOOK_NICK_ACCOUNT, &nw_hook_account);
334 deregisterhook(HOOK_NICK_LOSTNICK, &nw_hook_lostnick);
335 deregisterhook(HOOK_NICK_RENAME, &nw_hook_rename);
336 deregisterhook(HOOK_NICK_MODECHANGE, &nw_hook_umodechange);
337 deregisterhook(HOOK_NICK_MESSAGE, &nw_hook_message);
338 deregisterhook(HOOK_CHANNEL_CREATE, &nw_hook_joinchannel);
339 deregisterhook(HOOK_CHANNEL_JOIN, &nw_hook_joinchannel);
340
341 deleteallschedules(&nw_sched_processevents);
342
343 /* Process all pending events */
344 nw_sched_processevents(NULL);
345
346 array_free(&nw_pendingnicks);
347
348 releasenickext(nickwatchext);
349
350 for (nw = nickwatches; nw; nw = next) {
351 next = nw->next;
352
353 parse_free(nw->tree);
354 free(nw);
355 }
356 }