]>
Commit | Line | Data |
---|---|---|
189935b1 | 1 | /* |
2 | * IRC - Internet Relay Chat, ircd/gline.c | |
3 | * Copyright (C) 1990 Jarkko Oikarinen and | |
4 | * University of Oulu, Finland | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2, or (at your option) | |
9 | * any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
19 | */ | |
20 | /** @file | |
21 | * @brief Implementation of Gline manipulation functions. | |
9f8856e9 | 22 | * @version $Id: gline.c,v 1.61.2.1 2006/06/09 02:12:25 entrope Exp $ |
189935b1 | 23 | */ |
24 | #include "config.h" | |
25 | ||
26 | #include "gline.h" | |
27 | #include "client.h" | |
28 | #include "ircd.h" | |
29 | #include "ircd_alloc.h" | |
30 | #include "ircd_features.h" | |
31 | #include "ircd_log.h" | |
32 | #include "ircd_reply.h" | |
33 | #include "ircd_snprintf.h" | |
34 | #include "ircd_string.h" | |
35 | #include "match.h" | |
36 | #include "numeric.h" | |
37 | #include "s_bsd.h" | |
38 | #include "s_debug.h" | |
39 | #include "s_misc.h" | |
40 | #include "s_stats.h" | |
41 | #include "send.h" | |
42 | #include "struct.h" | |
43 | #include "msg.h" | |
44 | #include "numnicks.h" | |
45 | #include "numeric.h" | |
46 | #include "whocmds.h" | |
47 | ||
48 | /* #include <assert.h> -- Now using assert in ircd_log.h */ | |
49 | #include <string.h> | |
50 | #include <stdio.h> | |
51 | #include <stdlib.h> | |
52 | ||
53 | #define CHECK_APPROVED 0 /**< Mask is acceptable */ | |
54 | #define CHECK_OVERRIDABLE 1 /**< Mask is acceptable, but not by default */ | |
55 | #define CHECK_REJECTED 2 /**< Mask is totally unacceptable */ | |
56 | ||
57 | #define MASK_WILD_0 0x01 /**< Wildcards in the last position */ | |
58 | #define MASK_WILD_1 0x02 /**< Wildcards in the next-to-last position */ | |
59 | ||
60 | #define MASK_WILD_MASK 0x03 /**< Mask out the positional wildcards */ | |
61 | ||
62 | #define MASK_WILDS 0x10 /**< Mask contains wildcards */ | |
63 | #define MASK_IP 0x20 /**< Mask is an IP address */ | |
64 | #define MASK_HALT 0x40 /**< Finished processing mask */ | |
65 | ||
66 | /** List of user G-lines. */ | |
67 | struct Gline* GlobalGlineList = 0; | |
68 | /** List of BadChan G-lines. */ | |
69 | struct Gline* BadChanGlineList = 0; | |
70 | ||
71 | /** Find canonical user and host for a string. | |
72 | * If \a userhost starts with '$', assign \a userhost to *user_p and NULL to *host_p. | |
73 | * Otherwise, if \a userhost contains '@', assign the earlier part of it to *user_p and the rest to *host_p. | |
74 | * Otherwise, assign \a def_user to *user_p and \a userhost to *host_p. | |
75 | * | |
76 | * @param[in] userhost Input string from user. | |
77 | * @param[out] user_p Gets pointer to user (or channel/realname) part of hostmask. | |
78 | * @param[out] host_p Gets point to host part of hostmask (may be assigned NULL). | |
79 | * @param[in] def_user Default value for user part. | |
80 | */ | |
81 | static void | |
82 | canon_userhost(char *userhost, char **user_p, char **host_p, char *def_user) | |
83 | { | |
84 | char *tmp; | |
85 | ||
86 | if (*userhost == '$') { | |
87 | *user_p = userhost; | |
88 | *host_p = NULL; | |
89 | return; | |
90 | } | |
91 | ||
92 | if (!(tmp = strchr(userhost, '@'))) { | |
93 | *user_p = def_user; | |
94 | *host_p = userhost; | |
95 | } else { | |
96 | *user_p = userhost; | |
97 | *(tmp++) = '\0'; | |
98 | *host_p = tmp; | |
99 | } | |
100 | } | |
101 | ||
102 | /** Create a Gline structure. | |
103 | * @param[in] user User part of mask. | |
104 | * @param[in] host Host part of mask (NULL if not applicable). | |
105 | * @param[in] reason Reason for G-line. | |
106 | * @param[in] expire Expiration timestamp. | |
107 | * @param[in] lastmod Last modification timestamp. | |
108 | * @param[in] flags Bitwise combination of GLINE_* bits. | |
109 | * @return Newly allocated G-line. | |
110 | */ | |
111 | static struct Gline * | |
112 | make_gline(char *user, char *host, char *reason, time_t expire, time_t lastmod, | |
113 | unsigned int flags) | |
114 | { | |
115 | struct Gline *gline, *sgline, *after = 0; | |
116 | ||
117 | if (!(flags & GLINE_BADCHAN)) { /* search for overlapping glines first */ | |
118 | ||
119 | for (gline = GlobalGlineList; gline; gline = sgline) { | |
120 | sgline = gline->gl_next; | |
121 | ||
122 | if (gline->gl_expire <= CurrentTime) | |
123 | gline_free(gline); | |
124 | else if (((gline->gl_flags & GLINE_LOCAL) != (flags & GLINE_LOCAL)) || | |
125 | (gline->gl_host && !host) || (!gline->gl_host && host)) | |
126 | continue; | |
127 | else if (!mmatch(gline->gl_user, user) /* gline contains new mask */ | |
128 | && (gline->gl_host == NULL || !mmatch(gline->gl_host, host))) { | |
129 | if (expire <= gline->gl_expire) /* will expire before wider gline */ | |
130 | return 0; | |
131 | else | |
132 | after = gline; /* stick new gline after this one */ | |
133 | } else if (!mmatch(user, gline->gl_user) /* new mask contains gline */ | |
134 | && (gline->gl_host==NULL || !mmatch(host, gline->gl_host)) | |
135 | && gline->gl_expire <= expire) /* old expires before new */ | |
136 | gline_free(gline); /* save some memory */ | |
137 | } | |
138 | } | |
139 | ||
140 | gline = (struct Gline *)MyMalloc(sizeof(struct Gline)); /* alloc memory */ | |
141 | assert(0 != gline); | |
142 | ||
143 | DupString(gline->gl_reason, reason); /* initialize gline... */ | |
144 | gline->gl_expire = expire; | |
145 | gline->gl_lastmod = lastmod; | |
146 | gline->gl_flags = flags & GLINE_MASK; | |
147 | ||
148 | if (flags & GLINE_BADCHAN) { /* set a BADCHAN gline */ | |
149 | DupString(gline->gl_user, user); /* first, remember channel */ | |
150 | gline->gl_host = 0; | |
151 | ||
152 | gline->gl_next = BadChanGlineList; /* then link it into list */ | |
153 | gline->gl_prev_p = &BadChanGlineList; | |
154 | if (BadChanGlineList) | |
155 | BadChanGlineList->gl_prev_p = &gline->gl_next; | |
156 | BadChanGlineList = gline; | |
157 | } else { | |
158 | DupString(gline->gl_user, user); /* remember them... */ | |
159 | if (*user != '$') | |
160 | DupString(gline->gl_host, host); | |
161 | else | |
162 | gline->gl_host = NULL; | |
163 | ||
164 | if (*user != '$' && ipmask_parse(host, &gline->gl_addr, &gline->gl_bits)) | |
165 | gline->gl_flags |= GLINE_IPMASK; | |
166 | ||
167 | if (after) { | |
168 | gline->gl_next = after->gl_next; | |
169 | gline->gl_prev_p = &after->gl_next; | |
170 | if (after->gl_next) | |
171 | after->gl_next->gl_prev_p = &gline->gl_next; | |
172 | after->gl_next = gline; | |
173 | } else { | |
174 | gline->gl_next = GlobalGlineList; /* then link it into list */ | |
175 | gline->gl_prev_p = &GlobalGlineList; | |
176 | if (GlobalGlineList) | |
177 | GlobalGlineList->gl_prev_p = &gline->gl_next; | |
178 | GlobalGlineList = gline; | |
179 | } | |
180 | } | |
181 | ||
182 | return gline; | |
183 | } | |
184 | ||
185 | /** Check local clients against a new G-line. | |
186 | * If the G-line is inactive or a badchan, return immediately. | |
187 | * Otherwise, if any users match it, disconnect them. | |
188 | * @param[in] cptr Peer connect that sent the G-line. | |
189 | * @param[in] sptr Client that originated the G-line. | |
190 | * @param[in] gline New G-line to check. | |
191 | * @return Zero, unless \a sptr G-lined himself, in which case CPTR_KILLED. | |
192 | */ | |
193 | static int | |
194 | do_gline(struct Client *cptr, struct Client *sptr, struct Gline *gline) | |
195 | { | |
196 | struct Client *acptr; | |
197 | int fd, retval = 0, tval; | |
198 | ||
199 | if (GlineIsBadChan(gline)) /* no action taken on badchan glines */ | |
200 | return 0; | |
201 | if (!GlineIsActive(gline)) /* no action taken on inactive glines */ | |
202 | return 0; | |
203 | ||
204 | for (fd = HighestFd; fd >= 0; --fd) { | |
205 | /* | |
206 | * get the users! | |
207 | */ | |
208 | if ((acptr = LocalClientArray[fd])) { | |
209 | if (!cli_user(acptr)) | |
210 | continue; | |
211 | ||
212 | if (GlineIsRealName(gline)) { /* Realname Gline */ | |
213 | Debug((DEBUG_DEBUG,"Realname Gline: %s %s",(cli_info(acptr)), | |
214 | gline->gl_user+2)); | |
215 | if (match(gline->gl_user+2, cli_info(acptr)) != 0) | |
216 | continue; | |
217 | Debug((DEBUG_DEBUG,"Matched!")); | |
218 | } else { /* Host/IP gline */ | |
219 | if (cli_user(acptr)->username && | |
220 | match(gline->gl_user, (cli_user(acptr))->username) != 0) | |
221 | continue; | |
222 | ||
223 | if (GlineIsIpMask(gline)) { | |
224 | if (!ipmask_check(&cli_ip(acptr), &gline->gl_addr, gline->gl_bits)) | |
225 | continue; | |
226 | } | |
227 | else { | |
228 | if (match(gline->gl_host, cli_sockhost(acptr)) != 0) | |
229 | continue; | |
230 | } | |
231 | } | |
232 | ||
233 | /* ok, here's one that got G-lined */ | |
234 | send_reply(acptr, SND_EXPLICIT | ERR_YOUREBANNEDCREEP, ":%s", | |
235 | gline->gl_reason); | |
236 | ||
237 | /* let the ops know about it */ | |
238 | sendto_opmask_butone(0, SNO_GLINE, "G-line active for %s", | |
239 | get_client_name(acptr, SHOW_IP)); | |
240 | ||
241 | /* and get rid of him */ | |
242 | if ((tval = exit_client_msg(cptr, acptr, &me, "G-lined (%s)", | |
243 | gline->gl_reason))) | |
244 | retval = tval; /* retain killed status */ | |
245 | } | |
246 | } | |
247 | return retval; | |
248 | } | |
249 | ||
250 | /** | |
251 | * Implements the mask checking applied to local G-lines. | |
252 | * Basically, host masks must have a minimum of two non-wild domain | |
253 | * fields, and IP masks must have a minimum of 16 bits. If the mask | |
254 | * has even one wild-card, OVERRIDABLE is returned, assuming the other | |
255 | * check doesn't fail. | |
256 | * @param[in] mask G-line mask to check. | |
257 | * @return One of CHECK_REJECTED, CHECK_OVERRIDABLE, or CHECK_APPROVED. | |
258 | */ | |
259 | static int | |
260 | gline_checkmask(char *mask) | |
261 | { | |
262 | unsigned int flags = MASK_IP; | |
263 | unsigned int dots = 0; | |
264 | unsigned int ipmask = 0; | |
265 | ||
266 | for (; *mask; mask++) { /* go through given mask */ | |
267 | if (*mask == '.') { /* it's a separator; advance positional wilds */ | |
268 | flags = (flags & ~MASK_WILD_MASK) | ((flags << 1) & MASK_WILD_MASK); | |
269 | dots++; | |
270 | ||
271 | if ((flags & (MASK_IP | MASK_WILDS)) == MASK_IP) | |
272 | ipmask += 8; /* It's an IP with no wilds, count bits */ | |
273 | } else if (*mask == '*' || *mask == '?') | |
274 | flags |= MASK_WILD_0 | MASK_WILDS; /* found a wildcard */ | |
275 | else if (*mask == '/') { /* n.n.n.n/n notation; parse bit specifier */ | |
276 | ++mask; | |
277 | ipmask = strtoul(mask, &mask, 10); | |
278 | ||
279 | /* sanity-check to date */ | |
280 | if (*mask || (flags & (MASK_WILDS | MASK_IP)) != MASK_IP) | |
281 | return CHECK_REJECTED; | |
282 | if (!dots) { | |
283 | if (ipmask > 128) | |
284 | return CHECK_REJECTED; | |
285 | if (ipmask < 128) | |
286 | flags |= MASK_WILDS; | |
287 | } else { | |
288 | if (dots != 3 || ipmask > 32) | |
289 | return CHECK_REJECTED; | |
290 | if (ipmask < 32) | |
291 | flags |= MASK_WILDS; | |
292 | } | |
293 | ||
294 | flags |= MASK_HALT; /* Halt the ipmask calculation */ | |
295 | break; /* get out of the loop */ | |
296 | } else if (!IsIP6Char(*mask)) { | |
297 | flags &= ~MASK_IP; /* not an IP anymore! */ | |
298 | ipmask = 0; | |
299 | } | |
300 | } | |
301 | ||
302 | /* Sanity-check quads */ | |
303 | if (dots > 3 || (!(flags & MASK_WILDS) && dots < 3)) { | |
304 | flags &= ~MASK_IP; | |
305 | ipmask = 0; | |
306 | } | |
307 | ||
308 | /* update bit count if necessary */ | |
309 | if ((flags & (MASK_IP | MASK_WILDS | MASK_HALT)) == MASK_IP) | |
310 | ipmask += 8; | |
311 | ||
312 | /* Check to see that it's not too wide of a mask */ | |
313 | if (flags & MASK_WILDS && | |
314 | ((!(flags & MASK_IP) && (dots < 2 || flags & MASK_WILD_MASK)) || | |
315 | (flags & MASK_IP && ipmask < 16))) | |
316 | return CHECK_REJECTED; /* to wide, reject */ | |
317 | ||
318 | /* Ok, it's approved; require override if it has wildcards, though */ | |
319 | return flags & MASK_WILDS ? CHECK_OVERRIDABLE : CHECK_APPROVED; | |
320 | } | |
321 | ||
322 | /** Forward a G-line to other servers. | |
323 | * @param[in] cptr Client that sent us the G-line. | |
324 | * @param[in] sptr Client that originated the G-line. | |
325 | * @param[in] gline G-line to forward. | |
326 | * @return Zero. | |
327 | */ | |
328 | int | |
329 | gline_propagate(struct Client *cptr, struct Client *sptr, struct Gline *gline) | |
330 | { | |
331 | if (GlineIsLocal(gline) || (IsUser(sptr) && !gline->gl_lastmod)) | |
332 | return 0; | |
333 | ||
334 | if (gline->gl_lastmod) | |
335 | sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s", | |
336 | GlineIsRemActive(gline) ? '+' : '-', gline->gl_user, | |
337 | gline->gl_host ? "@" : "", | |
338 | gline->gl_host ? gline->gl_host : "", | |
339 | gline->gl_expire - CurrentTime, gline->gl_lastmod, | |
340 | gline->gl_reason); | |
341 | else | |
342 | sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, | |
343 | (GlineIsRemActive(gline) ? | |
344 | "* +%s%s%s %Tu :%s" : "* -%s%s%s"), | |
345 | gline->gl_user, | |
346 | gline->gl_host ? "@" : "", | |
347 | gline->gl_host ? gline->gl_host : "", | |
348 | gline->gl_expire - CurrentTime, gline->gl_reason); | |
349 | ||
350 | return 0; | |
351 | } | |
352 | ||
353 | /** Create a new G-line and add it to global lists. | |
354 | * \a userhost may be in one of four forms: | |
355 | * \li A channel name, to add a BadChan. | |
356 | * \li A string starting with $R and followed by a mask to match against their realname. | |
357 | * \li A user\@IP mask (user\@ part optional) to create an IP-based ban. | |
358 | * \li A user\@host mask (user\@ part optional) to create a hostname ban. | |
359 | * | |
360 | * @param[in] cptr Client that sent us the G-line. | |
361 | * @param[in] sptr Client that originated the G-line. | |
362 | * @param[in] userhost Text mask for the G-line. | |
363 | * @param[in] reason Reason for G-line. | |
364 | * @param[in] expire Duration of G-line in seconds. | |
365 | * @param[in] lastmod Last modification time of G-line. | |
366 | * @param[in] flags Bitwise combination of GLINE_* flags. | |
367 | * @return Zero or CPTR_KILLED, depending on whether \a sptr is suicidal. | |
368 | */ | |
369 | int | |
370 | gline_add(struct Client *cptr, struct Client *sptr, char *userhost, | |
371 | char *reason, time_t expire, time_t lastmod, unsigned int flags) | |
372 | { | |
373 | struct Gline *agline; | |
374 | char uhmask[USERLEN + HOSTLEN + 2]; | |
375 | char *user, *host; | |
376 | int tmp; | |
377 | ||
378 | assert(0 != userhost); | |
379 | assert(0 != reason); | |
380 | ||
381 | if (*userhost == '#' || *userhost == '&') { | |
382 | if ((flags & GLINE_LOCAL) && !HasPriv(sptr, PRIV_LOCAL_BADCHAN)) | |
383 | return send_reply(sptr, ERR_NOPRIVILEGES); | |
384 | ||
385 | flags |= GLINE_BADCHAN; | |
386 | user = userhost; | |
387 | host = 0; | |
388 | } else if (*userhost == '$') { | |
389 | switch (*userhost == '$' ? userhost[1] : userhost[3]) { | |
390 | case 'R': flags |= GLINE_REALNAME; break; | |
391 | default: | |
392 | /* uh, what to do here? */ | |
393 | /* The answer, my dear Watson, is we throw a protocol_violation() | |
394 | -- hikari */ | |
395 | if (IsServer(cptr)) | |
396 | return protocol_violation(sptr,"%s has been smoking the sweet leaf and sent me a whacky gline",cli_name(sptr)); | |
397 | else { | |
398 | sendto_opmask_butone(NULL, SNO_GLINE, "%s has been smoking the sweet leaf and sent me a whacky gline", cli_name(sptr)); | |
399 | return 0; | |
400 | } | |
401 | break; | |
402 | } | |
403 | user = (*userhost =='$' ? userhost : userhost+2); | |
404 | host = 0; | |
405 | } else { | |
406 | canon_userhost(userhost, &user, &host, "*"); | |
407 | if (sizeof(uhmask) < | |
408 | ircd_snprintf(0, uhmask, sizeof(uhmask), "%s@%s", user, host)) | |
409 | return send_reply(sptr, ERR_LONGMASK); | |
410 | else if (MyUser(sptr) || (IsUser(sptr) && flags & GLINE_LOCAL)) { | |
411 | switch (gline_checkmask(host)) { | |
412 | case CHECK_OVERRIDABLE: /* oper overrided restriction */ | |
413 | if (flags & GLINE_OPERFORCE) | |
414 | break; | |
415 | /*FALLTHROUGH*/ | |
416 | case CHECK_REJECTED: | |
417 | return send_reply(sptr, ERR_MASKTOOWIDE, uhmask); | |
418 | break; | |
419 | } | |
420 | ||
421 | if ((tmp = count_users(uhmask)) >= | |
422 | feature_int(FEAT_GLINEMAXUSERCOUNT) && !(flags & GLINE_OPERFORCE)) | |
423 | return send_reply(sptr, ERR_TOOMANYUSERS, tmp); | |
424 | } | |
425 | } | |
426 | ||
427 | /* | |
428 | * You cannot set a negative (or zero) expire time, nor can you set an | |
429 | * expiration time for greater than GLINE_MAX_EXPIRE. | |
430 | */ | |
431 | if (!(flags & GLINE_FORCE) && (expire <= 0 || expire > GLINE_MAX_EXPIRE)) { | |
432 | if (!IsServer(sptr) && MyConnect(sptr)) | |
433 | send_reply(sptr, ERR_BADEXPIRE, expire); | |
434 | return 0; | |
435 | } | |
436 | ||
437 | expire += CurrentTime; /* convert from lifetime to timestamp */ | |
438 | ||
439 | /* Inform ops... */ | |
440 | sendto_opmask_butone(0, ircd_strncmp(reason, "AUTO", 4) ? SNO_GLINE : | |
441 | SNO_AUTO, "%s adding %s %s for %s%s%s, expiring at " | |
442 | "%Tu: %s", | |
443 | (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ? | |
444 | cli_name(sptr) : | |
445 | cli_name((cli_user(sptr))->server), | |
446 | (flags & GLINE_LOCAL) ? "local" : "global", | |
447 | (flags & GLINE_BADCHAN) ? "BADCHAN" : "GLINE", user, | |
448 | (flags & (GLINE_BADCHAN|GLINE_REALNAME)) ? "" : "@", | |
449 | (flags & (GLINE_BADCHAN|GLINE_REALNAME)) ? "" : host, | |
450 | expire + TSoffset, reason); | |
451 | ||
452 | /* and log it */ | |
453 | log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE, | |
454 | "%#C adding %s %s for %s%s%s, expiring at %Tu: %s", sptr, | |
455 | flags & GLINE_LOCAL ? "local" : "global", | |
456 | flags & GLINE_BADCHAN ? "BADCHAN" : "GLINE", user, | |
457 | flags & (GLINE_BADCHAN|GLINE_REALNAME) ? "" : "@", | |
458 | flags & (GLINE_BADCHAN|GLINE_REALNAME) ? "" : host, | |
459 | expire + TSoffset, reason); | |
460 | ||
461 | /* make the gline */ | |
462 | agline = make_gline(user, host, reason, expire, lastmod, flags); | |
463 | ||
464 | if (!agline) /* if it overlapped, silently return */ | |
465 | return 0; | |
466 | ||
467 | gline_propagate(cptr, sptr, agline); | |
468 | ||
469 | return do_gline(cptr, sptr, agline); /* knock off users if necessary */ | |
470 | } | |
471 | ||
472 | /** Activate a currently inactive G-line. | |
473 | * @param[in] cptr Peer that told us to activate the G-line. | |
474 | * @param[in] sptr Client that originally thought it was a good idea. | |
475 | * @param[in] gline G-line to activate. | |
476 | * @param[in] lastmod New value for last modification timestamp. | |
477 | * @param[in] flags 0 if the activation should be propagated, GLINE_LOCAL if not. | |
478 | * @return Zero, unless \a sptr had a death wish (in which case CPTR_KILLED). | |
479 | */ | |
480 | int | |
481 | gline_activate(struct Client *cptr, struct Client *sptr, struct Gline *gline, | |
482 | time_t lastmod, unsigned int flags) | |
483 | { | |
484 | unsigned int saveflags = 0; | |
485 | ||
486 | assert(0 != gline); | |
487 | ||
488 | saveflags = gline->gl_flags; | |
489 | ||
490 | if (flags & GLINE_LOCAL) | |
491 | gline->gl_flags &= ~GLINE_LDEACT; | |
492 | else { | |
493 | gline->gl_flags |= GLINE_ACTIVE; | |
494 | ||
495 | if (gline->gl_lastmod) { | |
496 | if (gline->gl_lastmod >= lastmod) /* force lastmod to increase */ | |
497 | gline->gl_lastmod++; | |
498 | else | |
499 | gline->gl_lastmod = lastmod; | |
500 | } | |
501 | } | |
502 | ||
503 | if ((saveflags & GLINE_ACTMASK) == GLINE_ACTIVE) | |
504 | return 0; /* was active to begin with */ | |
505 | ||
506 | /* Inform ops and log it */ | |
507 | sendto_opmask_butone(0, SNO_GLINE, "%s activating global %s for %s%s%s, " | |
508 | "expiring at %Tu: %s", | |
509 | (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ? | |
510 | cli_name(sptr) : | |
511 | cli_name((cli_user(sptr))->server), | |
512 | GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", | |
513 | gline->gl_user, gline->gl_host ? "@" : "", | |
514 | gline->gl_host ? gline->gl_host : "", | |
515 | gline->gl_expire + TSoffset, gline->gl_reason); | |
516 | ||
517 | log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE, | |
518 | "%#C activating global %s for %s%s%s, expiring at %Tu: %s", sptr, | |
519 | GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user, | |
520 | gline->gl_host ? "@" : "", | |
521 | gline->gl_host ? gline->gl_host : "", | |
522 | gline->gl_expire + TSoffset, gline->gl_reason); | |
523 | ||
524 | if (!(flags & GLINE_LOCAL)) /* don't propagate local changes */ | |
525 | gline_propagate(cptr, sptr, gline); | |
526 | ||
527 | return do_gline(cptr, sptr, gline); | |
528 | } | |
529 | ||
530 | /** Deactivate a G-line. | |
531 | * @param[in] cptr Peer that gave us the message. | |
532 | * @param[in] sptr Client that initiated the deactivation. | |
533 | * @param[in] gline G-line to deactivate. | |
534 | * @param[in] lastmod New value for G-line last modification timestamp. | |
535 | * @param[in] flags GLINE_LOCAL to only deactivate locally, 0 to propagate. | |
536 | * @return Zero. | |
537 | */ | |
538 | int | |
539 | gline_deactivate(struct Client *cptr, struct Client *sptr, struct Gline *gline, | |
540 | time_t lastmod, unsigned int flags) | |
541 | { | |
542 | unsigned int saveflags = 0; | |
543 | char *msg; | |
544 | ||
545 | assert(0 != gline); | |
546 | ||
547 | saveflags = gline->gl_flags; | |
548 | ||
549 | if (GlineIsLocal(gline)) | |
550 | msg = "removing local"; | |
551 | else if (!gline->gl_lastmod && !(flags & GLINE_LOCAL)) { | |
552 | msg = "removing global"; | |
553 | gline->gl_flags &= ~GLINE_ACTIVE; /* propagate a -<mask> */ | |
554 | } else { | |
555 | msg = "deactivating global"; | |
556 | ||
557 | if (flags & GLINE_LOCAL) | |
558 | gline->gl_flags |= GLINE_LDEACT; | |
559 | else { | |
560 | gline->gl_flags &= ~GLINE_ACTIVE; | |
561 | ||
562 | if (gline->gl_lastmod) { | |
563 | if (gline->gl_lastmod >= lastmod) | |
564 | gline->gl_lastmod++; | |
565 | else | |
566 | gline->gl_lastmod = lastmod; | |
567 | } | |
568 | } | |
569 | ||
570 | if ((saveflags & GLINE_ACTMASK) != GLINE_ACTIVE) | |
571 | return 0; /* was inactive to begin with */ | |
572 | } | |
573 | ||
574 | /* Inform ops and log it */ | |
575 | sendto_opmask_butone(0, SNO_GLINE, "%s %s %s for %s%s%s, expiring at %Tu: " | |
576 | "%s", | |
577 | (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ? | |
578 | cli_name(sptr) : | |
579 | cli_name((cli_user(sptr))->server), | |
580 | msg, GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", | |
581 | gline->gl_user, gline->gl_host ? "@" : "", | |
582 | gline->gl_host ? gline->gl_host : "", | |
583 | gline->gl_expire + TSoffset, gline->gl_reason); | |
584 | ||
585 | log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE, | |
586 | "%#C %s %s for %s%s%s, expiring at %Tu: %s", sptr, msg, | |
587 | GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user, | |
588 | gline->gl_host ? "@" : "", | |
589 | gline->gl_host ? gline->gl_host : "", | |
590 | gline->gl_expire + TSoffset, gline->gl_reason); | |
591 | ||
592 | if (!(flags & GLINE_LOCAL)) /* don't propagate local changes */ | |
593 | gline_propagate(cptr, sptr, gline); | |
594 | ||
595 | /* if it's a local gline or a Uworld gline (and not locally deactivated).. */ | |
596 | if (GlineIsLocal(gline) || (!gline->gl_lastmod && !(flags & GLINE_LOCAL))) | |
597 | gline_free(gline); /* get rid of it */ | |
598 | ||
599 | return 0; | |
600 | } | |
601 | ||
602 | /** Find a G-line for a particular mask, guided by certain flags. | |
603 | * Certain bits in \a flags are interpreted specially: | |
604 | * <dl> | |
605 | * <dt>GLINE_ANY</dt><dd>Search both BadChans and user G-lines.</dd> | |
606 | * <dt>GLINE_BADCHAN</dt><dd>Search BadChans.</dd> | |
607 | * <dt>GLINE_GLOBAL</dt><dd>Only match global G-lines.</dd> | |
608 | * <dt>GLINE_LASTMOD</dt><dd>Only match G-lines with a last modification time.</dd> | |
609 | * <dt>GLINE_EXACT</dt><dd>Require an exact match of G-line mask.</dd> | |
610 | * <dt>anything else</dt><dd>Search user G-lines.</dd> | |
611 | * </dl> | |
612 | * @param[in] userhost Mask to search for. | |
613 | * @param[in] flags Bitwise combination of GLINE_* flags. | |
614 | * @return First matching G-line, or NULL if none are found. | |
615 | */ | |
616 | struct Gline * | |
617 | gline_find(char *userhost, unsigned int flags) | |
618 | { | |
619 | struct Gline *gline; | |
620 | struct Gline *sgline; | |
621 | char *user, *host, *t_uh; | |
622 | ||
623 | if (flags & (GLINE_BADCHAN | GLINE_ANY)) { | |
624 | for (gline = BadChanGlineList; gline; gline = sgline) { | |
625 | sgline = gline->gl_next; | |
626 | ||
627 | if (gline->gl_expire <= CurrentTime) | |
628 | gline_free(gline); | |
629 | else if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) || | |
630 | (flags & GLINE_LASTMOD && !gline->gl_lastmod)) | |
631 | continue; | |
632 | else if ((flags & GLINE_EXACT ? ircd_strcmp(gline->gl_user, userhost) : | |
633 | match(gline->gl_user, userhost)) == 0) | |
634 | return gline; | |
635 | } | |
636 | } | |
637 | ||
638 | if ((flags & (GLINE_BADCHAN | GLINE_ANY)) == GLINE_BADCHAN || | |
639 | *userhost == '#' || *userhost == '&') | |
640 | return 0; | |
641 | ||
642 | DupString(t_uh, userhost); | |
643 | canon_userhost(t_uh, &user, &host, "*"); | |
644 | ||
645 | for (gline = GlobalGlineList; gline; gline = sgline) { | |
646 | sgline = gline->gl_next; | |
647 | ||
648 | if (gline->gl_expire <= CurrentTime) | |
649 | gline_free(gline); | |
650 | else if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) || | |
651 | (flags & GLINE_LASTMOD && !gline->gl_lastmod)) | |
652 | continue; | |
653 | else if (flags & GLINE_EXACT) { | |
654 | if (((gline->gl_host && host && ircd_strcmp(gline->gl_host, host) == 0) | |
655 | || (!gline->gl_host && !host)) && | |
656 | (ircd_strcmp(gline->gl_user, user) == 0)) | |
657 | break; | |
658 | } else { | |
659 | if (((gline->gl_host && host && match(gline->gl_host, host) == 0) | |
660 | || (!gline->gl_host && !host)) && | |
661 | (match(gline->gl_user, user) == 0)) | |
662 | break; | |
663 | } | |
664 | } | |
665 | ||
666 | MyFree(t_uh); | |
667 | ||
668 | return gline; | |
669 | } | |
670 | ||
671 | /** Find a matching G-line for a user. | |
672 | * @param[in] cptr Client to compare against. | |
673 | * @param[in] flags Bitwise combination of GLINE_GLOBAL and/or | |
674 | * GLINE_LASTMOD to limit matches. | |
675 | * @return Matching G-line, or NULL if none are found. | |
676 | */ | |
677 | struct Gline * | |
678 | gline_lookup(struct Client *cptr, unsigned int flags) | |
679 | { | |
680 | struct Gline *gline; | |
681 | struct Gline *sgline; | |
682 | ||
683 | for (gline = GlobalGlineList; gline; gline = sgline) { | |
684 | sgline = gline->gl_next; | |
685 | ||
686 | if (gline->gl_expire <= CurrentTime) { | |
687 | gline_free(gline); | |
688 | continue; | |
689 | } | |
690 | ||
691 | if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) || | |
692 | (flags & GLINE_LASTMOD && !gline->gl_lastmod)) | |
693 | continue; | |
694 | ||
695 | if (GlineIsRealName(gline)) { | |
696 | Debug((DEBUG_DEBUG,"realname gline: '%s' '%s'",gline->gl_user,cli_info(cptr))); | |
697 | if (match(gline->gl_user+2, cli_info(cptr)) != 0) | |
698 | continue; | |
699 | } | |
700 | else { | |
701 | if (match(gline->gl_user, (cli_user(cptr))->username) != 0) | |
702 | continue; | |
703 | ||
704 | if (GlineIsIpMask(gline)) { | |
705 | if (!ipmask_check(&cli_ip(cptr), &gline->gl_addr, gline->gl_bits)) | |
706 | continue; | |
707 | } | |
708 | else { | |
709 | if (match(gline->gl_host, (cli_user(cptr))->realhost) != 0) | |
710 | continue; | |
711 | } | |
712 | } | |
713 | if (GlineIsActive(gline)) | |
714 | return gline; | |
715 | } | |
716 | /* | |
717 | * No Glines matched | |
718 | */ | |
719 | return 0; | |
720 | } | |
721 | ||
722 | /** Delink and free a G-line. | |
723 | * @param[in] gline G-line to free. | |
724 | */ | |
725 | void | |
726 | gline_free(struct Gline *gline) | |
727 | { | |
728 | assert(0 != gline); | |
729 | ||
730 | *gline->gl_prev_p = gline->gl_next; /* squeeze this gline out */ | |
731 | if (gline->gl_next) | |
732 | gline->gl_next->gl_prev_p = gline->gl_prev_p; | |
733 | ||
734 | MyFree(gline->gl_user); /* free up the memory */ | |
735 | if (gline->gl_host) | |
736 | MyFree(gline->gl_host); | |
737 | MyFree(gline->gl_reason); | |
738 | MyFree(gline); | |
739 | } | |
740 | ||
741 | /** Burst all known global G-lines to another server. | |
742 | * @param[in] cptr Destination of burst. | |
743 | */ | |
744 | void | |
745 | gline_burst(struct Client *cptr) | |
746 | { | |
747 | struct Gline *gline; | |
748 | struct Gline *sgline; | |
749 | ||
750 | for (gline = GlobalGlineList; gline; gline = sgline) { /* all glines */ | |
751 | sgline = gline->gl_next; | |
752 | ||
753 | if (gline->gl_expire <= CurrentTime) /* expire any that need expiring */ | |
754 | gline_free(gline); | |
755 | else if (!GlineIsLocal(gline) && gline->gl_lastmod) | |
756 | sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s", | |
757 | GlineIsRemActive(gline) ? '+' : '-', gline->gl_user, | |
758 | gline->gl_host ? "@" : "", | |
759 | gline->gl_host ? gline->gl_host : "", | |
760 | gline->gl_expire - CurrentTime, gline->gl_lastmod, | |
761 | gline->gl_reason); | |
762 | } | |
763 | ||
764 | for (gline = BadChanGlineList; gline; gline = sgline) { /* all glines */ | |
765 | sgline = gline->gl_next; | |
766 | ||
767 | if (gline->gl_expire <= CurrentTime) /* expire any that need expiring */ | |
768 | gline_free(gline); | |
769 | else if (!GlineIsLocal(gline) && gline->gl_lastmod) | |
770 | sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s %Tu %Tu :%s", | |
771 | GlineIsRemActive(gline) ? '+' : '-', gline->gl_user, | |
772 | gline->gl_expire - CurrentTime, gline->gl_lastmod, | |
773 | gline->gl_reason); | |
774 | } | |
775 | } | |
776 | ||
777 | /** Send a G-line to another server. | |
778 | * @param[in] cptr Who to inform of the G-line. | |
779 | * @param[in] gline G-line to send. | |
780 | * @return Zero. | |
781 | */ | |
782 | int | |
783 | gline_resend(struct Client *cptr, struct Gline *gline) | |
784 | { | |
785 | if (GlineIsLocal(gline) || !gline->gl_lastmod) | |
786 | return 0; | |
787 | ||
788 | sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s", | |
789 | GlineIsRemActive(gline) ? '+' : '-', gline->gl_user, | |
790 | gline->gl_host ? "@" : "", | |
791 | gline->gl_host ? gline->gl_host : "", | |
792 | gline->gl_expire - CurrentTime, gline->gl_lastmod, | |
793 | gline->gl_reason); | |
794 | ||
795 | return 0; | |
796 | } | |
797 | ||
798 | /** Display one or all G-lines to a user. | |
799 | * If \a userhost is not NULL, only send the first matching G-line. | |
800 | * Otherwise send the whole list. | |
801 | * @param[in] sptr User asking for G-line list. | |
802 | * @param[in] userhost G-line mask to search for (or NULL). | |
803 | * @return Zero. | |
804 | */ | |
805 | int | |
806 | gline_list(struct Client *sptr, char *userhost) | |
807 | { | |
808 | struct Gline *gline; | |
809 | struct Gline *sgline; | |
810 | ||
811 | if (userhost) { | |
812 | if (!(gline = gline_find(userhost, GLINE_ANY))) /* no such gline */ | |
813 | return send_reply(sptr, ERR_NOSUCHGLINE, userhost); | |
814 | ||
815 | /* send gline information along */ | |
816 | send_reply(sptr, RPL_GLIST, gline->gl_user, | |
817 | gline->gl_host ? "@" : "", | |
818 | gline->gl_host ? gline->gl_host : "", | |
819 | gline->gl_expire + TSoffset, | |
820 | GlineIsLocal(gline) ? cli_name(&me) : "*", | |
821 | GlineIsActive(gline) ? '+' : '-', gline->gl_reason); | |
822 | } else { | |
823 | for (gline = GlobalGlineList; gline; gline = sgline) { | |
824 | sgline = gline->gl_next; | |
825 | ||
826 | if (gline->gl_expire <= CurrentTime) | |
827 | gline_free(gline); | |
828 | else | |
829 | send_reply(sptr, RPL_GLIST, gline->gl_user, | |
830 | gline->gl_host ? "@" : "", | |
831 | gline->gl_host ? gline->gl_host : "", | |
832 | gline->gl_expire + TSoffset, | |
833 | GlineIsLocal(gline) ? cli_name(&me) : "*", | |
834 | GlineIsActive(gline) ? '+' : '-', gline->gl_reason); | |
835 | } | |
836 | ||
837 | for (gline = BadChanGlineList; gline; gline = sgline) { | |
838 | sgline = gline->gl_next; | |
839 | ||
840 | if (gline->gl_expire <= CurrentTime) | |
841 | gline_free(gline); | |
842 | else | |
843 | send_reply(sptr, RPL_GLIST, gline->gl_user, "", "", | |
844 | gline->gl_expire + TSoffset, | |
845 | GlineIsLocal(gline) ? cli_name(&me) : "*", | |
846 | GlineIsActive(gline) ? '+' : '-', gline->gl_reason); | |
847 | } | |
848 | } | |
849 | ||
850 | /* end of gline information */ | |
851 | return send_reply(sptr, RPL_ENDOFGLIST); | |
852 | } | |
853 | ||
854 | /** Statistics callback to list G-lines. | |
855 | * @param[in] sptr Client requesting statistics. | |
856 | * @param[in] sd Stats descriptor for request (ignored). | |
857 | * @param[in] param Extra parameter from user (ignored). | |
858 | */ | |
859 | void | |
860 | gline_stats(struct Client *sptr, const struct StatDesc *sd, | |
861 | char *param) | |
862 | { | |
863 | struct Gline *gline; | |
864 | struct Gline *sgline; | |
865 | ||
866 | for (gline = GlobalGlineList; gline; gline = sgline) { | |
867 | sgline = gline->gl_next; | |
868 | ||
869 | if (gline->gl_expire <= CurrentTime) | |
870 | gline_free(gline); | |
871 | else | |
872 | send_reply(sptr, RPL_STATSGLINE, 'G', gline->gl_user, | |
873 | gline->gl_host ? "@" : "", | |
874 | gline->gl_host ? gline->gl_host : "", | |
9f8856e9 | 875 | gline->gl_expire + TSoffset, |
876 | GlineIsActive(gline) ? '+' : '-', | |
877 | gline->gl_reason); | |
189935b1 | 878 | } |
879 | } | |
880 | ||
881 | /** Calculate memory used by G-lines. | |
882 | * @param[out] gl_size Number of bytes used by G-lines. | |
883 | * @return Number of G-lines in use. | |
884 | */ | |
885 | int | |
886 | gline_memory_count(size_t *gl_size) | |
887 | { | |
888 | struct Gline *gline; | |
889 | unsigned int gl = 0; | |
890 | ||
891 | for (gline = GlobalGlineList; gline; gline = gline->gl_next) | |
892 | { | |
893 | gl++; | |
894 | *gl_size += sizeof(struct Gline); | |
895 | *gl_size += gline->gl_user ? (strlen(gline->gl_user) + 1) : 0; | |
896 | *gl_size += gline->gl_host ? (strlen(gline->gl_host) + 1) : 0; | |
897 | *gl_size += gline->gl_reason ? (strlen(gline->gl_reason) + 1) : 0; | |
898 | } | |
899 | return gl; | |
900 | } |