]>
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. | |
22 | * @version $Id: gline.c,v 1.61 2005/09/22 20:42:21 entrope Exp $ | |
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 */ | |
4d98fe75 | 242 | /* Asuka - Reimplement HEAD_IN_SAND_GLINE from Lain */ |
243 | if ((tval = exit_client_msg(cptr, acptr, &me, | |
244 | feature_bool(FEAT_HIS_GLINE) ? "G-lined" : "G-lined (%s)", gline->gl_reason))) | |
245 | retval = tval; /* retain killed status */ | |
189935b1 | 246 | } |
247 | } | |
248 | return retval; | |
249 | } | |
250 | ||
251 | /** | |
252 | * Implements the mask checking applied to local G-lines. | |
253 | * Basically, host masks must have a minimum of two non-wild domain | |
254 | * fields, and IP masks must have a minimum of 16 bits. If the mask | |
255 | * has even one wild-card, OVERRIDABLE is returned, assuming the other | |
256 | * check doesn't fail. | |
257 | * @param[in] mask G-line mask to check. | |
258 | * @return One of CHECK_REJECTED, CHECK_OVERRIDABLE, or CHECK_APPROVED. | |
259 | */ | |
260 | static int | |
261 | gline_checkmask(char *mask) | |
262 | { | |
263 | unsigned int flags = MASK_IP; | |
264 | unsigned int dots = 0; | |
265 | unsigned int ipmask = 0; | |
266 | ||
267 | for (; *mask; mask++) { /* go through given mask */ | |
268 | if (*mask == '.') { /* it's a separator; advance positional wilds */ | |
269 | flags = (flags & ~MASK_WILD_MASK) | ((flags << 1) & MASK_WILD_MASK); | |
270 | dots++; | |
271 | ||
272 | if ((flags & (MASK_IP | MASK_WILDS)) == MASK_IP) | |
273 | ipmask += 8; /* It's an IP with no wilds, count bits */ | |
274 | } else if (*mask == '*' || *mask == '?') | |
275 | flags |= MASK_WILD_0 | MASK_WILDS; /* found a wildcard */ | |
276 | else if (*mask == '/') { /* n.n.n.n/n notation; parse bit specifier */ | |
277 | ++mask; | |
278 | ipmask = strtoul(mask, &mask, 10); | |
279 | ||
280 | /* sanity-check to date */ | |
281 | if (*mask || (flags & (MASK_WILDS | MASK_IP)) != MASK_IP) | |
282 | return CHECK_REJECTED; | |
283 | if (!dots) { | |
284 | if (ipmask > 128) | |
285 | return CHECK_REJECTED; | |
286 | if (ipmask < 128) | |
287 | flags |= MASK_WILDS; | |
288 | } else { | |
289 | if (dots != 3 || ipmask > 32) | |
290 | return CHECK_REJECTED; | |
291 | if (ipmask < 32) | |
292 | flags |= MASK_WILDS; | |
293 | } | |
294 | ||
295 | flags |= MASK_HALT; /* Halt the ipmask calculation */ | |
296 | break; /* get out of the loop */ | |
297 | } else if (!IsIP6Char(*mask)) { | |
298 | flags &= ~MASK_IP; /* not an IP anymore! */ | |
299 | ipmask = 0; | |
300 | } | |
301 | } | |
302 | ||
303 | /* Sanity-check quads */ | |
304 | if (dots > 3 || (!(flags & MASK_WILDS) && dots < 3)) { | |
305 | flags &= ~MASK_IP; | |
306 | ipmask = 0; | |
307 | } | |
308 | ||
309 | /* update bit count if necessary */ | |
310 | if ((flags & (MASK_IP | MASK_WILDS | MASK_HALT)) == MASK_IP) | |
311 | ipmask += 8; | |
312 | ||
313 | /* Check to see that it's not too wide of a mask */ | |
314 | if (flags & MASK_WILDS && | |
315 | ((!(flags & MASK_IP) && (dots < 2 || flags & MASK_WILD_MASK)) || | |
316 | (flags & MASK_IP && ipmask < 16))) | |
317 | return CHECK_REJECTED; /* to wide, reject */ | |
318 | ||
319 | /* Ok, it's approved; require override if it has wildcards, though */ | |
320 | return flags & MASK_WILDS ? CHECK_OVERRIDABLE : CHECK_APPROVED; | |
321 | } | |
322 | ||
323 | /** Forward a G-line to other servers. | |
324 | * @param[in] cptr Client that sent us the G-line. | |
325 | * @param[in] sptr Client that originated the G-line. | |
326 | * @param[in] gline G-line to forward. | |
327 | * @return Zero. | |
328 | */ | |
329 | int | |
330 | gline_propagate(struct Client *cptr, struct Client *sptr, struct Gline *gline) | |
331 | { | |
332 | if (GlineIsLocal(gline) || (IsUser(sptr) && !gline->gl_lastmod)) | |
333 | return 0; | |
334 | ||
335 | if (gline->gl_lastmod) | |
336 | sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s", | |
337 | GlineIsRemActive(gline) ? '+' : '-', gline->gl_user, | |
338 | gline->gl_host ? "@" : "", | |
339 | gline->gl_host ? gline->gl_host : "", | |
340 | gline->gl_expire - CurrentTime, gline->gl_lastmod, | |
341 | gline->gl_reason); | |
342 | else | |
343 | sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, | |
344 | (GlineIsRemActive(gline) ? | |
345 | "* +%s%s%s %Tu :%s" : "* -%s%s%s"), | |
346 | gline->gl_user, | |
347 | gline->gl_host ? "@" : "", | |
348 | gline->gl_host ? gline->gl_host : "", | |
349 | gline->gl_expire - CurrentTime, gline->gl_reason); | |
350 | ||
351 | return 0; | |
352 | } | |
353 | ||
354 | /** Create a new G-line and add it to global lists. | |
355 | * \a userhost may be in one of four forms: | |
356 | * \li A channel name, to add a BadChan. | |
357 | * \li A string starting with $R and followed by a mask to match against their realname. | |
358 | * \li A user\@IP mask (user\@ part optional) to create an IP-based ban. | |
359 | * \li A user\@host mask (user\@ part optional) to create a hostname ban. | |
360 | * | |
361 | * @param[in] cptr Client that sent us the G-line. | |
362 | * @param[in] sptr Client that originated the G-line. | |
363 | * @param[in] userhost Text mask for the G-line. | |
364 | * @param[in] reason Reason for G-line. | |
365 | * @param[in] expire Duration of G-line in seconds. | |
366 | * @param[in] lastmod Last modification time of G-line. | |
367 | * @param[in] flags Bitwise combination of GLINE_* flags. | |
368 | * @return Zero or CPTR_KILLED, depending on whether \a sptr is suicidal. | |
369 | */ | |
370 | int | |
371 | gline_add(struct Client *cptr, struct Client *sptr, char *userhost, | |
372 | char *reason, time_t expire, time_t lastmod, unsigned int flags) | |
373 | { | |
374 | struct Gline *agline; | |
375 | char uhmask[USERLEN + HOSTLEN + 2]; | |
376 | char *user, *host; | |
377 | int tmp; | |
378 | ||
379 | assert(0 != userhost); | |
380 | assert(0 != reason); | |
381 | ||
382 | if (*userhost == '#' || *userhost == '&') { | |
383 | if ((flags & GLINE_LOCAL) && !HasPriv(sptr, PRIV_LOCAL_BADCHAN)) | |
384 | return send_reply(sptr, ERR_NOPRIVILEGES); | |
385 | ||
386 | flags |= GLINE_BADCHAN; | |
387 | user = userhost; | |
388 | host = 0; | |
389 | } else if (*userhost == '$') { | |
390 | switch (*userhost == '$' ? userhost[1] : userhost[3]) { | |
391 | case 'R': flags |= GLINE_REALNAME; break; | |
392 | default: | |
393 | /* uh, what to do here? */ | |
394 | /* The answer, my dear Watson, is we throw a protocol_violation() | |
395 | -- hikari */ | |
396 | if (IsServer(cptr)) | |
397 | return protocol_violation(sptr,"%s has been smoking the sweet leaf and sent me a whacky gline",cli_name(sptr)); | |
398 | else { | |
399 | sendto_opmask_butone(NULL, SNO_GLINE, "%s has been smoking the sweet leaf and sent me a whacky gline", cli_name(sptr)); | |
400 | return 0; | |
401 | } | |
402 | break; | |
403 | } | |
404 | user = (*userhost =='$' ? userhost : userhost+2); | |
405 | host = 0; | |
406 | } else { | |
407 | canon_userhost(userhost, &user, &host, "*"); | |
408 | if (sizeof(uhmask) < | |
409 | ircd_snprintf(0, uhmask, sizeof(uhmask), "%s@%s", user, host)) | |
410 | return send_reply(sptr, ERR_LONGMASK); | |
411 | else if (MyUser(sptr) || (IsUser(sptr) && flags & GLINE_LOCAL)) { | |
412 | switch (gline_checkmask(host)) { | |
413 | case CHECK_OVERRIDABLE: /* oper overrided restriction */ | |
414 | if (flags & GLINE_OPERFORCE) | |
415 | break; | |
416 | /*FALLTHROUGH*/ | |
417 | case CHECK_REJECTED: | |
418 | return send_reply(sptr, ERR_MASKTOOWIDE, uhmask); | |
419 | break; | |
420 | } | |
421 | ||
422 | if ((tmp = count_users(uhmask)) >= | |
423 | feature_int(FEAT_GLINEMAXUSERCOUNT) && !(flags & GLINE_OPERFORCE)) | |
424 | return send_reply(sptr, ERR_TOOMANYUSERS, tmp); | |
425 | } | |
426 | } | |
427 | ||
428 | /* | |
429 | * You cannot set a negative (or zero) expire time, nor can you set an | |
430 | * expiration time for greater than GLINE_MAX_EXPIRE. | |
431 | */ | |
432 | if (!(flags & GLINE_FORCE) && (expire <= 0 || expire > GLINE_MAX_EXPIRE)) { | |
433 | if (!IsServer(sptr) && MyConnect(sptr)) | |
434 | send_reply(sptr, ERR_BADEXPIRE, expire); | |
435 | return 0; | |
436 | } | |
437 | ||
438 | expire += CurrentTime; /* convert from lifetime to timestamp */ | |
439 | ||
440 | /* Inform ops... */ | |
441 | sendto_opmask_butone(0, ircd_strncmp(reason, "AUTO", 4) ? SNO_GLINE : | |
442 | SNO_AUTO, "%s adding %s %s for %s%s%s, expiring at " | |
443 | "%Tu: %s", | |
444 | (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ? | |
445 | cli_name(sptr) : | |
446 | cli_name((cli_user(sptr))->server), | |
447 | (flags & GLINE_LOCAL) ? "local" : "global", | |
448 | (flags & GLINE_BADCHAN) ? "BADCHAN" : "GLINE", user, | |
449 | (flags & (GLINE_BADCHAN|GLINE_REALNAME)) ? "" : "@", | |
450 | (flags & (GLINE_BADCHAN|GLINE_REALNAME)) ? "" : host, | |
451 | expire + TSoffset, reason); | |
452 | ||
453 | /* and log it */ | |
454 | log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE, | |
455 | "%#C adding %s %s for %s%s%s, expiring at %Tu: %s", sptr, | |
456 | flags & GLINE_LOCAL ? "local" : "global", | |
457 | flags & GLINE_BADCHAN ? "BADCHAN" : "GLINE", user, | |
458 | flags & (GLINE_BADCHAN|GLINE_REALNAME) ? "" : "@", | |
459 | flags & (GLINE_BADCHAN|GLINE_REALNAME) ? "" : host, | |
460 | expire + TSoffset, reason); | |
461 | ||
462 | /* make the gline */ | |
463 | agline = make_gline(user, host, reason, expire, lastmod, flags); | |
464 | ||
465 | if (!agline) /* if it overlapped, silently return */ | |
466 | return 0; | |
467 | ||
468 | gline_propagate(cptr, sptr, agline); | |
469 | ||
470 | return do_gline(cptr, sptr, agline); /* knock off users if necessary */ | |
471 | } | |
472 | ||
473 | /** Activate a currently inactive G-line. | |
474 | * @param[in] cptr Peer that told us to activate the G-line. | |
475 | * @param[in] sptr Client that originally thought it was a good idea. | |
476 | * @param[in] gline G-line to activate. | |
477 | * @param[in] lastmod New value for last modification timestamp. | |
478 | * @param[in] flags 0 if the activation should be propagated, GLINE_LOCAL if not. | |
479 | * @return Zero, unless \a sptr had a death wish (in which case CPTR_KILLED). | |
480 | */ | |
481 | int | |
482 | gline_activate(struct Client *cptr, struct Client *sptr, struct Gline *gline, | |
483 | time_t lastmod, unsigned int flags) | |
484 | { | |
485 | unsigned int saveflags = 0; | |
486 | ||
487 | assert(0 != gline); | |
488 | ||
489 | saveflags = gline->gl_flags; | |
490 | ||
491 | if (flags & GLINE_LOCAL) | |
492 | gline->gl_flags &= ~GLINE_LDEACT; | |
493 | else { | |
494 | gline->gl_flags |= GLINE_ACTIVE; | |
495 | ||
496 | if (gline->gl_lastmod) { | |
497 | if (gline->gl_lastmod >= lastmod) /* force lastmod to increase */ | |
498 | gline->gl_lastmod++; | |
499 | else | |
500 | gline->gl_lastmod = lastmod; | |
501 | } | |
502 | } | |
503 | ||
504 | if ((saveflags & GLINE_ACTMASK) == GLINE_ACTIVE) | |
505 | return 0; /* was active to begin with */ | |
506 | ||
507 | /* Inform ops and log it */ | |
508 | sendto_opmask_butone(0, SNO_GLINE, "%s activating global %s for %s%s%s, " | |
509 | "expiring at %Tu: %s", | |
510 | (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ? | |
511 | cli_name(sptr) : | |
512 | cli_name((cli_user(sptr))->server), | |
513 | GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", | |
514 | gline->gl_user, gline->gl_host ? "@" : "", | |
515 | gline->gl_host ? gline->gl_host : "", | |
516 | gline->gl_expire + TSoffset, gline->gl_reason); | |
517 | ||
518 | log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE, | |
519 | "%#C activating global %s for %s%s%s, expiring at %Tu: %s", sptr, | |
520 | GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user, | |
521 | gline->gl_host ? "@" : "", | |
522 | gline->gl_host ? gline->gl_host : "", | |
523 | gline->gl_expire + TSoffset, gline->gl_reason); | |
524 | ||
525 | if (!(flags & GLINE_LOCAL)) /* don't propagate local changes */ | |
526 | gline_propagate(cptr, sptr, gline); | |
527 | ||
528 | return do_gline(cptr, sptr, gline); | |
529 | } | |
530 | ||
531 | /** Deactivate a G-line. | |
532 | * @param[in] cptr Peer that gave us the message. | |
533 | * @param[in] sptr Client that initiated the deactivation. | |
534 | * @param[in] gline G-line to deactivate. | |
535 | * @param[in] lastmod New value for G-line last modification timestamp. | |
536 | * @param[in] flags GLINE_LOCAL to only deactivate locally, 0 to propagate. | |
537 | * @return Zero. | |
538 | */ | |
539 | int | |
540 | gline_deactivate(struct Client *cptr, struct Client *sptr, struct Gline *gline, | |
541 | time_t lastmod, unsigned int flags) | |
542 | { | |
543 | unsigned int saveflags = 0; | |
544 | char *msg; | |
545 | ||
546 | assert(0 != gline); | |
547 | ||
548 | saveflags = gline->gl_flags; | |
549 | ||
550 | if (GlineIsLocal(gline)) | |
551 | msg = "removing local"; | |
552 | else if (!gline->gl_lastmod && !(flags & GLINE_LOCAL)) { | |
553 | msg = "removing global"; | |
554 | gline->gl_flags &= ~GLINE_ACTIVE; /* propagate a -<mask> */ | |
555 | } else { | |
556 | msg = "deactivating global"; | |
557 | ||
558 | if (flags & GLINE_LOCAL) | |
559 | gline->gl_flags |= GLINE_LDEACT; | |
560 | else { | |
561 | gline->gl_flags &= ~GLINE_ACTIVE; | |
562 | ||
563 | if (gline->gl_lastmod) { | |
564 | if (gline->gl_lastmod >= lastmod) | |
565 | gline->gl_lastmod++; | |
566 | else | |
567 | gline->gl_lastmod = lastmod; | |
568 | } | |
569 | } | |
570 | ||
571 | if ((saveflags & GLINE_ACTMASK) != GLINE_ACTIVE) | |
572 | return 0; /* was inactive to begin with */ | |
573 | } | |
574 | ||
575 | /* Inform ops and log it */ | |
576 | sendto_opmask_butone(0, SNO_GLINE, "%s %s %s for %s%s%s, expiring at %Tu: " | |
577 | "%s", | |
578 | (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ? | |
579 | cli_name(sptr) : | |
580 | cli_name((cli_user(sptr))->server), | |
581 | msg, GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", | |
582 | gline->gl_user, gline->gl_host ? "@" : "", | |
583 | gline->gl_host ? gline->gl_host : "", | |
584 | gline->gl_expire + TSoffset, gline->gl_reason); | |
585 | ||
586 | log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE, | |
587 | "%#C %s %s for %s%s%s, expiring at %Tu: %s", sptr, msg, | |
588 | GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user, | |
589 | gline->gl_host ? "@" : "", | |
590 | gline->gl_host ? gline->gl_host : "", | |
591 | gline->gl_expire + TSoffset, gline->gl_reason); | |
592 | ||
593 | if (!(flags & GLINE_LOCAL)) /* don't propagate local changes */ | |
594 | gline_propagate(cptr, sptr, gline); | |
595 | ||
596 | /* if it's a local gline or a Uworld gline (and not locally deactivated).. */ | |
597 | if (GlineIsLocal(gline) || (!gline->gl_lastmod && !(flags & GLINE_LOCAL))) | |
598 | gline_free(gline); /* get rid of it */ | |
599 | ||
600 | return 0; | |
601 | } | |
602 | ||
603 | /** Find a G-line for a particular mask, guided by certain flags. | |
604 | * Certain bits in \a flags are interpreted specially: | |
605 | * <dl> | |
606 | * <dt>GLINE_ANY</dt><dd>Search both BadChans and user G-lines.</dd> | |
607 | * <dt>GLINE_BADCHAN</dt><dd>Search BadChans.</dd> | |
608 | * <dt>GLINE_GLOBAL</dt><dd>Only match global G-lines.</dd> | |
609 | * <dt>GLINE_LASTMOD</dt><dd>Only match G-lines with a last modification time.</dd> | |
610 | * <dt>GLINE_EXACT</dt><dd>Require an exact match of G-line mask.</dd> | |
611 | * <dt>anything else</dt><dd>Search user G-lines.</dd> | |
612 | * </dl> | |
613 | * @param[in] userhost Mask to search for. | |
614 | * @param[in] flags Bitwise combination of GLINE_* flags. | |
615 | * @return First matching G-line, or NULL if none are found. | |
616 | */ | |
617 | struct Gline * | |
618 | gline_find(char *userhost, unsigned int flags) | |
619 | { | |
620 | struct Gline *gline; | |
621 | struct Gline *sgline; | |
622 | char *user, *host, *t_uh; | |
623 | ||
624 | if (flags & (GLINE_BADCHAN | GLINE_ANY)) { | |
625 | for (gline = BadChanGlineList; gline; gline = sgline) { | |
626 | sgline = gline->gl_next; | |
627 | ||
628 | if (gline->gl_expire <= CurrentTime) | |
629 | gline_free(gline); | |
630 | else if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) || | |
631 | (flags & GLINE_LASTMOD && !gline->gl_lastmod)) | |
632 | continue; | |
633 | else if ((flags & GLINE_EXACT ? ircd_strcmp(gline->gl_user, userhost) : | |
634 | match(gline->gl_user, userhost)) == 0) | |
635 | return gline; | |
636 | } | |
637 | } | |
638 | ||
639 | if ((flags & (GLINE_BADCHAN | GLINE_ANY)) == GLINE_BADCHAN || | |
640 | *userhost == '#' || *userhost == '&') | |
641 | return 0; | |
642 | ||
643 | DupString(t_uh, userhost); | |
644 | canon_userhost(t_uh, &user, &host, "*"); | |
645 | ||
646 | for (gline = GlobalGlineList; gline; gline = sgline) { | |
647 | sgline = gline->gl_next; | |
648 | ||
649 | if (gline->gl_expire <= CurrentTime) | |
650 | gline_free(gline); | |
651 | else if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) || | |
652 | (flags & GLINE_LASTMOD && !gline->gl_lastmod)) | |
653 | continue; | |
654 | else if (flags & GLINE_EXACT) { | |
655 | if (((gline->gl_host && host && ircd_strcmp(gline->gl_host, host) == 0) | |
656 | || (!gline->gl_host && !host)) && | |
657 | (ircd_strcmp(gline->gl_user, user) == 0)) | |
658 | break; | |
659 | } else { | |
660 | if (((gline->gl_host && host && match(gline->gl_host, host) == 0) | |
661 | || (!gline->gl_host && !host)) && | |
662 | (match(gline->gl_user, user) == 0)) | |
663 | break; | |
664 | } | |
665 | } | |
666 | ||
667 | MyFree(t_uh); | |
668 | ||
669 | return gline; | |
670 | } | |
671 | ||
672 | /** Find a matching G-line for a user. | |
673 | * @param[in] cptr Client to compare against. | |
674 | * @param[in] flags Bitwise combination of GLINE_GLOBAL and/or | |
675 | * GLINE_LASTMOD to limit matches. | |
676 | * @return Matching G-line, or NULL if none are found. | |
677 | */ | |
678 | struct Gline * | |
679 | gline_lookup(struct Client *cptr, unsigned int flags) | |
680 | { | |
681 | struct Gline *gline; | |
682 | struct Gline *sgline; | |
683 | ||
684 | for (gline = GlobalGlineList; gline; gline = sgline) { | |
685 | sgline = gline->gl_next; | |
686 | ||
687 | if (gline->gl_expire <= CurrentTime) { | |
688 | gline_free(gline); | |
689 | continue; | |
690 | } | |
691 | ||
692 | if ((flags & GLINE_GLOBAL && gline->gl_flags & GLINE_LOCAL) || | |
693 | (flags & GLINE_LASTMOD && !gline->gl_lastmod)) | |
694 | continue; | |
695 | ||
696 | if (GlineIsRealName(gline)) { | |
697 | Debug((DEBUG_DEBUG,"realname gline: '%s' '%s'",gline->gl_user,cli_info(cptr))); | |
698 | if (match(gline->gl_user+2, cli_info(cptr)) != 0) | |
699 | continue; | |
700 | } | |
701 | else { | |
702 | if (match(gline->gl_user, (cli_user(cptr))->username) != 0) | |
703 | continue; | |
704 | ||
705 | if (GlineIsIpMask(gline)) { | |
706 | if (!ipmask_check(&cli_ip(cptr), &gline->gl_addr, gline->gl_bits)) | |
707 | continue; | |
708 | } | |
709 | else { | |
710 | if (match(gline->gl_host, (cli_user(cptr))->realhost) != 0) | |
711 | continue; | |
712 | } | |
713 | } | |
714 | if (GlineIsActive(gline)) | |
715 | return gline; | |
716 | } | |
717 | /* | |
718 | * No Glines matched | |
719 | */ | |
720 | return 0; | |
721 | } | |
722 | ||
723 | /** Delink and free a G-line. | |
724 | * @param[in] gline G-line to free. | |
725 | */ | |
726 | void | |
727 | gline_free(struct Gline *gline) | |
728 | { | |
729 | assert(0 != gline); | |
730 | ||
731 | *gline->gl_prev_p = gline->gl_next; /* squeeze this gline out */ | |
732 | if (gline->gl_next) | |
733 | gline->gl_next->gl_prev_p = gline->gl_prev_p; | |
734 | ||
735 | MyFree(gline->gl_user); /* free up the memory */ | |
736 | if (gline->gl_host) | |
737 | MyFree(gline->gl_host); | |
738 | MyFree(gline->gl_reason); | |
739 | MyFree(gline); | |
740 | } | |
741 | ||
742 | /** Burst all known global G-lines to another server. | |
743 | * @param[in] cptr Destination of burst. | |
744 | */ | |
745 | void | |
746 | gline_burst(struct Client *cptr) | |
747 | { | |
748 | struct Gline *gline; | |
749 | struct Gline *sgline; | |
750 | ||
751 | for (gline = GlobalGlineList; gline; gline = sgline) { /* all glines */ | |
752 | sgline = gline->gl_next; | |
753 | ||
754 | if (gline->gl_expire <= CurrentTime) /* expire any that need expiring */ | |
755 | gline_free(gline); | |
756 | else if (!GlineIsLocal(gline) && gline->gl_lastmod) | |
757 | sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s", | |
758 | GlineIsRemActive(gline) ? '+' : '-', gline->gl_user, | |
759 | gline->gl_host ? "@" : "", | |
760 | gline->gl_host ? gline->gl_host : "", | |
761 | gline->gl_expire - CurrentTime, gline->gl_lastmod, | |
762 | gline->gl_reason); | |
763 | } | |
764 | ||
765 | for (gline = BadChanGlineList; gline; gline = sgline) { /* all glines */ | |
766 | sgline = gline->gl_next; | |
767 | ||
768 | if (gline->gl_expire <= CurrentTime) /* expire any that need expiring */ | |
769 | gline_free(gline); | |
770 | else if (!GlineIsLocal(gline) && gline->gl_lastmod) | |
771 | sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s %Tu %Tu :%s", | |
772 | GlineIsRemActive(gline) ? '+' : '-', gline->gl_user, | |
773 | gline->gl_expire - CurrentTime, gline->gl_lastmod, | |
774 | gline->gl_reason); | |
775 | } | |
776 | } | |
777 | ||
778 | /** Send a G-line to another server. | |
779 | * @param[in] cptr Who to inform of the G-line. | |
780 | * @param[in] gline G-line to send. | |
781 | * @return Zero. | |
782 | */ | |
783 | int | |
784 | gline_resend(struct Client *cptr, struct Gline *gline) | |
785 | { | |
786 | if (GlineIsLocal(gline) || !gline->gl_lastmod) | |
787 | return 0; | |
788 | ||
789 | sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s", | |
790 | GlineIsRemActive(gline) ? '+' : '-', gline->gl_user, | |
791 | gline->gl_host ? "@" : "", | |
792 | gline->gl_host ? gline->gl_host : "", | |
793 | gline->gl_expire - CurrentTime, gline->gl_lastmod, | |
794 | gline->gl_reason); | |
795 | ||
796 | return 0; | |
797 | } | |
798 | ||
799 | /** Display one or all G-lines to a user. | |
800 | * If \a userhost is not NULL, only send the first matching G-line. | |
801 | * Otherwise send the whole list. | |
802 | * @param[in] sptr User asking for G-line list. | |
803 | * @param[in] userhost G-line mask to search for (or NULL). | |
804 | * @return Zero. | |
805 | */ | |
806 | int | |
807 | gline_list(struct Client *sptr, char *userhost) | |
808 | { | |
809 | struct Gline *gline; | |
810 | struct Gline *sgline; | |
811 | ||
812 | if (userhost) { | |
813 | if (!(gline = gline_find(userhost, GLINE_ANY))) /* no such gline */ | |
814 | return send_reply(sptr, ERR_NOSUCHGLINE, userhost); | |
815 | ||
816 | /* send gline information along */ | |
817 | send_reply(sptr, RPL_GLIST, gline->gl_user, | |
818 | gline->gl_host ? "@" : "", | |
819 | gline->gl_host ? gline->gl_host : "", | |
820 | gline->gl_expire + TSoffset, | |
821 | GlineIsLocal(gline) ? cli_name(&me) : "*", | |
822 | GlineIsActive(gline) ? '+' : '-', gline->gl_reason); | |
823 | } else { | |
824 | for (gline = GlobalGlineList; gline; gline = sgline) { | |
825 | sgline = gline->gl_next; | |
826 | ||
827 | if (gline->gl_expire <= CurrentTime) | |
828 | gline_free(gline); | |
829 | else | |
830 | send_reply(sptr, RPL_GLIST, gline->gl_user, | |
831 | gline->gl_host ? "@" : "", | |
832 | gline->gl_host ? gline->gl_host : "", | |
833 | gline->gl_expire + TSoffset, | |
834 | GlineIsLocal(gline) ? cli_name(&me) : "*", | |
835 | GlineIsActive(gline) ? '+' : '-', gline->gl_reason); | |
836 | } | |
837 | ||
838 | for (gline = BadChanGlineList; gline; gline = sgline) { | |
839 | sgline = gline->gl_next; | |
840 | ||
841 | if (gline->gl_expire <= CurrentTime) | |
842 | gline_free(gline); | |
843 | else | |
844 | send_reply(sptr, RPL_GLIST, gline->gl_user, "", "", | |
845 | gline->gl_expire + TSoffset, | |
846 | GlineIsLocal(gline) ? cli_name(&me) : "*", | |
847 | GlineIsActive(gline) ? '+' : '-', gline->gl_reason); | |
848 | } | |
849 | } | |
850 | ||
851 | /* end of gline information */ | |
852 | return send_reply(sptr, RPL_ENDOFGLIST); | |
853 | } | |
854 | ||
855 | /** Statistics callback to list G-lines. | |
856 | * @param[in] sptr Client requesting statistics. | |
857 | * @param[in] sd Stats descriptor for request (ignored). | |
858 | * @param[in] param Extra parameter from user (ignored). | |
859 | */ | |
860 | void | |
861 | gline_stats(struct Client *sptr, const struct StatDesc *sd, | |
862 | char *param) | |
863 | { | |
864 | struct Gline *gline; | |
865 | struct Gline *sgline; | |
866 | ||
867 | for (gline = GlobalGlineList; gline; gline = sgline) { | |
868 | sgline = gline->gl_next; | |
869 | ||
870 | if (gline->gl_expire <= CurrentTime) | |
871 | gline_free(gline); | |
872 | else | |
873 | send_reply(sptr, RPL_STATSGLINE, 'G', gline->gl_user, | |
874 | gline->gl_host ? "@" : "", | |
875 | gline->gl_host ? gline->gl_host : "", | |
876 | gline->gl_expire + TSoffset, gline->gl_reason); | |
877 | } | |
878 | } | |
879 | ||
880 | /** Calculate memory used by G-lines. | |
881 | * @param[out] gl_size Number of bytes used by G-lines. | |
882 | * @return Number of G-lines in use. | |
883 | */ | |
884 | int | |
885 | gline_memory_count(size_t *gl_size) | |
886 | { | |
887 | struct Gline *gline; | |
888 | unsigned int gl = 0; | |
889 | ||
890 | for (gline = GlobalGlineList; gline; gline = gline->gl_next) | |
891 | { | |
892 | gl++; | |
893 | *gl_size += sizeof(struct Gline); | |
894 | *gl_size += gline->gl_user ? (strlen(gline->gl_user) + 1) : 0; | |
895 | *gl_size += gline->gl_host ? (strlen(gline->gl_host) + 1) : 0; | |
896 | *gl_size += gline->gl_reason ? (strlen(gline->gl_reason) + 1) : 0; | |
897 | } | |
898 | return gl; | |
899 | } |