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