]> jfr.im git - irc/quakenet/newserv.git/blob - glines/glines_buf.c
Implement notice flag +G for automated gline messages.
[irc/quakenet/newserv.git] / glines / glines_buf.c
1 #include <stdarg.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <assert.h>
5 #include "../lib/array.h"
6 #include "../lib/irc_string.h"
7 #include "../irc/irc.h"
8 #include "../control/control.h"
9 #include "../trusts/trusts.h"
10 #include "glines.h"
11
12 static int nextglinebufid = 1;
13 glinebuf *glinebuflog[MAXGLINELOG] = {};
14 int glinebuflogoffset = 0;
15
16 void glinebufinit(glinebuf *gbuf, int id) {
17 gbuf->id = id;
18 gbuf->comment = NULL;
19 gbuf->commit = 0;
20 gbuf->amend = 0;
21 gbuf->glines = NULL;
22 gbuf->hitsvalid = 1;
23 gbuf->userhits = 0;
24 gbuf->channelhits = 0;
25 array_init(&gbuf->hits, sizeof(sstring *));
26 }
27
28 gline *glinebufadd(glinebuf *gbuf, const char *mask, const char *creator, const char *reason, time_t expire, time_t lastmod, time_t lifetime) {
29 gline *gl;
30
31 gl = makegline(mask); /* sets up nick,user,host,node and flags */
32
33 if (!gl) {
34 /* couldn't process gline mask */
35 Error("gline", ERR_WARNING, "Tried to add malformed G-Line %s!", mask);
36 return 0;
37 }
38
39 gl->creator = getsstring(creator, 255);
40
41 /* it's not unreasonable to assume gline is active, if we're adding a deactivated gline, we can remove this later */
42 gl->flags |= GLINE_ACTIVE;
43
44 gl->reason = getsstring(reason, 255);
45 gl->expire = expire;
46 gl->lastmod = lastmod;
47 gl->lifetime = lifetime;
48
49 gl->next = gbuf->glines;
50 gbuf->glines = gl;
51
52 gbuf->hitsvalid = 0;
53
54 return gl;
55 }
56
57 void glinebufaddbyip(glinebuf *gbuf, const char *user, struct irc_in_addr *ip, unsigned char bits, int flags, const char *creator, const char *reason, time_t expire, time_t lastmod, time_t lifetime) {
58 trusthost *th, *oth;
59 char mask[512];
60 unsigned char nodebits;
61
62 nodebits = getnodebits(ip);
63
64 if (!(flags & GLINE_IGNORE_TRUST)) {
65 th = th_getbyhost(ip);
66
67 if (th && (th->group->flags & TRUST_RELIABLE_USERNAME)) { /* Trust with reliable usernames */
68 for (oth = th->group->hosts; oth; oth = oth->next)
69 glinebufaddbyip(gbuf, user, &oth->ip, oth->bits, flags | GLINE_ALWAYS_USER | GLINE_IGNORE_TRUST, creator, reason, expire, lastmod, lifetime);
70
71 return;
72 }
73 }
74
75 if (!(flags & GLINE_ALWAYS_USER))
76 user = "*";
77
78 /* Widen gline to match the node mask. */
79 if (nodebits < bits)
80 bits = nodebits;
81
82 snprintf(mask, sizeof(mask), "%s@%s", user, trusts_cidr2str(ip, bits));
83
84 glinebufadd(gbuf, mask, creator, reason, expire, lastmod, lifetime);
85 }
86
87 void glinebufaddbynick(glinebuf *gbuf, nick *np, int flags, const char *creator, const char *reason, time_t expire, time_t lastmod, time_t lifetime) {
88 if (flags & GLINE_ALWAYS_NICK) {
89 char mask[512];
90 snprintf(mask, sizeof(mask), "%s!*@*", np->nick);
91 glinebufadd(gbuf, mask, creator, reason, expire, lastmod, lifetime);
92 } else {
93 glinebufaddbyip(gbuf, np->ident, &np->p_ipaddr, 128, flags, creator, reason, expire, lastmod, lifetime);
94 }
95 }
96
97 void glinebufcounthits(glinebuf *gbuf, int *users, int *channels) {
98 gline *gl;
99 int i, hit, slot;
100 chanindex *cip;
101 channel *cp;
102 nick *np;
103 char uhmask[512];
104
105 #if 0 /* Let's just do a new hit check anyway. */
106 if (gbuf->hitsvalid)
107 return;
108 #endif
109
110 gbuf->userhits = 0;
111 gbuf->channelhits = 0;
112
113 for (i = 0; i < gbuf->hits.cursi; i++)
114 freesstring(((sstring **)gbuf->hits.content)[i]);
115
116 array_free(&gbuf->hits);
117 array_init(&gbuf->hits, sizeof(sstring *));
118
119 for (i = 0; i<CHANNELHASHSIZE; i++) {
120 for (cip = chantable[i]; cip; cip = cip->next) {
121 cp = cip->channel;
122
123 if (!cp)
124 continue;
125
126 hit = 0;
127
128 for (gl = gbuf->glines; gl; gl = gl->next) {
129 if (gline_match_channel(gl, cp)) {
130 hit = 1;
131 break;
132 }
133 }
134
135 if (hit) {
136 snprintf(uhmask, sizeof(uhmask), "channel: %s", cip->name->content);
137
138 gbuf->channelhits++;
139
140 slot = array_getfreeslot(&gbuf->hits);
141 ((sstring **)gbuf->hits.content)[slot] = getsstring(uhmask, 512);
142 }
143 }
144 }
145
146 for (i = 0; i < NICKHASHSIZE; i++) {
147 for (np = nicktable[i]; np; np = np->next) {
148 hit = 0;
149
150 for (gl = gbuf->glines; gl; gl = gl->next) {
151 if (gline_match_nick(gl, np)) {
152 hit = 1;
153 break;
154 }
155 }
156
157 if (hit) {
158 snprintf(uhmask, sizeof(uhmask), "user: %s!%s@%s%s%s r(%s)", np->nick, np->ident, np->host->name->content,
159 (np->auth) ? "/" : "", (np->auth) ? np->authname : "", np->realname->name->content);
160
161 gbuf->userhits++;
162
163 slot = array_getfreeslot(&gbuf->hits);
164 ((sstring **)gbuf->hits.content)[slot] = getsstring(uhmask, 512);
165 }
166 }
167 }
168
169 gbuf->hitsvalid = 1;
170
171 if (users)
172 *users = gbuf->userhits;
173
174 if (channels)
175 *channels = gbuf->channelhits;
176 }
177
178 int glinebufchecksane(glinebuf *gbuf, nick *spewto, int overridesanity, int overridelimit) {
179 gline *gl;
180 int users, channels, good;
181 const char *hint;
182
183 glinebufcounthits(gbuf, &users, &channels);
184
185 if (!overridelimit) {
186 if (channels > MAXUSERGLINECHANNELHITS) {
187 controlreply(spewto, "G-Lines would hit %d channels. Limit is %d. Not setting G-Lines.", channels, MAXUSERGLINECHANNELHITS);
188 return 0;
189 } else if (users > MAXUSERGLINEUSERHITS) {
190 controlreply(spewto, "G-Lines would hit %d users. Limit is %d. Not setting G-Lines.", users, MAXUSERGLINEUSERHITS);
191 return 0;
192 }
193 }
194
195 good = 1;
196
197 if (!overridesanity) {
198 /* Remove glines that fail the sanity check */
199 for (gl = gbuf->glines; gl; gl = gl->next) {
200 if (!isglinesane(gl, &hint)) {
201 controlreply(spewto, "Sanity check failed for G-Line on '%s' - Skipping: %s",
202 glinetostring(gl), hint);
203 good = 0;
204 }
205 }
206 }
207
208 return good;
209 }
210
211 void glinebufspew(glinebuf *gbuf, nick *spewto) {
212 gline *gl;
213 int i;
214 char timebuf[30], lastmod[30];
215
216 if (!gbuf->hitsvalid)
217 glinebufcounthits(gbuf, NULL, NULL);
218
219 if (gbuf->id == 0)
220 controlreply(spewto, "Uncommitted G-Line transaction.");
221 else
222 controlreply(spewto, "G-Line transaction ID %d", gbuf->id);
223
224 controlreply(spewto, "Comment: %s", (gbuf->comment) ? gbuf->comment->content : "(no comment)");
225
226 if (gbuf->commit) {
227 strftime(timebuf, sizeof(timebuf), "%d/%m/%y %H:%M:%S", localtime(&gbuf->commit));
228 controlreply(spewto, "Committed at: %s", timebuf);
229 }
230
231 if (gbuf->amend) {
232 strftime(timebuf, sizeof(timebuf), "%d/%m/%y %H:%M:%S", localtime(&gbuf->amend));
233 controlreply(spewto, "Amended at: %s", timebuf);
234 }
235
236 controlreply(spewto, "Mask Expiry Last modified Creator Reason");
237
238 for (gl = gbuf->glines; gl; gl = gl->next) {
239 strftime(timebuf, sizeof(timebuf), "%d/%m/%y %H:%M:%S", localtime(&gl->expire));
240
241 if (gl->lastmod == 0)
242 strncpy(lastmod, "<ulined>", sizeof(lastmod));
243 else
244 strftime(lastmod, sizeof(lastmod), "%d/%m/%y %H:%M:%S", localtime(&gl->lastmod));
245
246 controlreply(spewto, "%-40s %-20s %-20s %-25s %s", glinetostring(gl), timebuf, lastmod, gl->creator->content, gl->reason ? gl->reason->content : "");
247 }
248
249 controlreply(spewto, "Hits");
250
251 for (i = 0; i < gbuf->hits.cursi; i++) {
252 controlreply(spewto, "%s", ((sstring **)gbuf->hits.content)[i]->content);
253
254 if (i >= 500) {
255 controlreply(spewto, "More than 500 hits, list truncated.");
256 break;
257 }
258 }
259
260 if (i == 0)
261 controlreply(spewto, "(no hits)");
262 }
263
264 void glinebufmerge(glinebuf *gbuf) {
265 /* TODO: reimplement */
266
267 /*
268 if (gbuf->merge) {
269 /-* Check if an existing gline supercedes this mask *-/
270 for (sgl = gbuf->glines; sgl; sgl = sgl->next) {
271 if (gline_match_mask(sgl, gl)) {
272 freegline(gl);
273 return sgl;
274 }
275 }
276
277 /-* Remove older glines this gline matches *-/
278 for (pnext = &gbuf->glines; *pnext; pnext = &((*pnext)->next)) {
279 sgl = *pnext;
280
281 if (gline_match_mask(gl, sgl)) {
282 *pnext = sgl->next;
283 freegline(sgl);
284
285 if (!*pnext)
286 break;
287 }
288 }
289 }
290 */
291 }
292
293 int glinebufcommit(glinebuf *gbuf, int propagate) {
294 gline *gl, *next, *sgl;
295 int users, channels, id;
296
297 /* Sanity check */
298 glinebufcounthits(gbuf, &users, &channels);
299
300 if (propagate && (users > MAXGLINEUSERHITS || channels > MAXGLINECHANNELHITS)) {
301 controlwall(NO_OPER, NL_GLINES_AUTO, "G-Line buffer would hit %d users/%d channels. Not setting G-Lines.");
302 glinebufabort(gbuf);
303 return 0;
304 }
305
306 /* Record the commit time */
307 time(&gbuf->commit);
308
309 id = glinebufwritelog(gbuf, propagate);
310
311 /* Move glines to the global gline list */
312 for (gl = gbuf->glines; gl; gl = next) {
313 next = gl->next;
314
315 sgl = findgline(glinetostring(gl));
316
317 if (sgl) {
318 /* existing gline
319 * in 1.4, can update expire, reason etc
320 * in 1.3 can only activate/deactivate an existing gline */
321
322 if (gl->flags & GLINE_ACTIVE && !(sgl->flags & GLINE_ACTIVE))
323 gline_activate(sgl, 0, 0);
324 else if (!(gl->flags & GLINE_ACTIVE) && sgl->flags & GLINE_ACTIVE)
325 gline_deactivate(sgl, 0, 0);
326
327 #if SNIRCD_VERSION >= 140
328 sgl->expire = gl->expire;
329
330 if (gl->lifetime > sgl->lifetime)
331 sgl->lifetime = gl->lifetime;
332
333 freesstring(sgl->reason);
334 sgl->reason = getsstring(gl->reason, 512);
335 #endif
336
337 freegline(gl);
338 gl = sgl;
339 } else {
340 gl->next = glinelist;
341 glinelist = gl;
342 }
343
344 gl->glinebufid = id;
345
346 if (propagate)
347 gline_propagate(gl);
348 }
349
350 /* We've moved all glines to the global gline list. Clear glines link in the glinebuf. */
351 gbuf->glines = NULL;
352
353 glinebufabort(gbuf);
354
355 return id;
356 }
357
358 void glinebufabort(glinebuf *gbuf) {
359 gline *gl, *next;
360 int i;
361
362 for (gl = gbuf->glines; gl; gl = next) {
363 next = gl->next;
364
365 freegline(gl);
366 }
367
368 freesstring(gbuf->comment);
369
370 for (i = 0; i < gbuf->hits.cursi; i++)
371 freesstring(((sstring **)gbuf->hits.content)[i]);
372
373 array_free(&gbuf->hits);
374 }
375
376 int glinebufundo(int id) {
377 glinebuf *gbl;
378 gline *gl, *sgl;
379 int i;
380
381 for (i = 0; i < MAXGLINELOG; i++) {
382 gbl = glinebuflog[i];
383
384 if (!gbl || gbl->id != id)
385 continue;
386
387 for (gl = gbl->glines; gl; gl = gl->next) {
388 sgl = findgline(glinetostring(gl));
389
390 if (!sgl)
391 continue;
392
393 sgl->glinebufid = 0;
394
395 gline_deactivate(sgl, 0, 1);
396 }
397
398 glinebufabort(gbl);
399 glinebuflog[i] = NULL;
400
401 return 1;
402 }
403
404 return 0;
405 }
406
407 void glinebufcommentf(glinebuf *gbuf, const char *format, ...) {
408 char comment[512];
409 va_list va;
410
411 va_start(va, format);
412 vsnprintf(comment, 511, format, va);
413 comment[511] = '\0';
414 va_end(va);
415
416 gbuf->comment = getsstring(comment, 512);
417 }
418
419 void glinebufcommentv(glinebuf *gbuf, const char *prefix, int cargc, char **cargv) {
420 char comment[512];
421 int i;
422
423 if (prefix)
424 strncpy(comment, prefix, sizeof(comment));
425 else
426 comment[0] = '\0';
427
428 for (i = 0; i < cargc; i++) {
429 if (comment[0])
430 strncat(comment, " ", sizeof(comment));
431
432 strncat(comment, cargv[i], sizeof(comment));
433 }
434
435 comment[511] = '\0';
436
437 gbuf->comment = getsstring(comment, 512);
438 }
439
440 int glinebufwritelog(glinebuf *gbuf, int propagating) {
441 int i, slot;
442 gline *gl, *sgl;
443 glinebuf *gbl;
444
445 if (!gbuf->glines)
446 return 0; /* Don't waste log IDs on empty gline buffers */
447
448 gbl = NULL;
449
450 /* Find an existing log buffer with the same id */
451 if (gbuf->id != 0) {
452 for (i = 0; i < MAXGLINELOG; i++) {
453 if (!glinebuflog[i])
454 continue;
455
456 if (glinebuflog[i]->id == gbuf->id) {
457 gbl = glinebuflog[i];
458 gbl->amend = gbuf->commit;
459
460 /* We're going to re-insert this glinebuf, so remove it for now */
461 glinebuflog[i] = NULL;
462
463 break;
464 }
465 }
466 }
467
468 /* Find a recent glinebuf that's a close match */
469 if (!gbl && !propagating) {
470 for (i = 0; i < MAXGLINELOG; i++) {
471 if (!glinebuflog[i])
472 continue;
473
474 if (glinebuflog[i]->commit < getnettime() - 5 && glinebuflog[i]->amend < getnettime() - 5)
475 continue;
476
477 assert(glinebuflog[i]->glines);
478
479 if (strcmp(glinebuflog[i]->glines->creator->content, gbuf->glines->creator->content) != 0)
480 continue;
481
482 gbl = glinebuflog[i];
483 gbl->amend = gbuf->commit;
484
485 /* We're going to re-insert this glinebuf, so remove it for now */
486 glinebuflog[i] = NULL;
487
488 break;
489 }
490 }
491
492 /* Make a new glinebuf for the log */
493 if (!gbl && gbuf->id == 0) {
494 gbl = malloc(sizeof(glinebuf));
495 glinebufinit(gbl, (gbuf->id == 0) ? nextglinebufid++ : gbuf->id);
496
497 assert(gbl->hitsvalid);
498
499 if (gbuf->comment)
500 glinebufcommentf(gbl, "%s", gbuf->comment->content);
501 else if (!propagating)
502 glinebufcommentf(gbl, "G-Lines set by %s", gbuf->glines->creator->content);
503
504 gbl->commit = gbuf->commit;
505 }
506
507 /* Save a duplicate of the glines in the log buffer */
508 for (gl = gbuf->glines; gl; gl = gl->next) {
509 sgl = glinedup(gl);
510 sgl->next = gbl->glines;
511 gbl->glines = sgl;
512 }
513
514 gbl->userhits += gbuf->userhits;
515 gbl->channelhits += gbuf->channelhits;
516
517 assert(gbuf->userhits + gbuf->channelhits == gbuf->hits.cursi);
518
519 for (i = 0; i < gbuf->hits.cursi; i++) {
520 slot = array_getfreeslot(&gbl->hits);
521 ((sstring **)gbl->hits.content)[slot] = getsstring(((sstring **)gbuf->hits.content)[i]->content, 512);
522 }
523
524 /* Log the transaction */
525 glinebuflogoffset++;
526
527 if (glinebuflogoffset >= MAXGLINELOG)
528 glinebuflogoffset = 0;
529
530 if (glinebuflog[glinebuflogoffset])
531 glinebufabort(glinebuflog[glinebuflogoffset]);
532
533 glinebuflog[glinebuflogoffset] = gbl;
534
535 return gbl->id;
536 }