1 |
/* Routines to check validity of JOINs and mode changes. |
2 |
* |
3 |
* IRC Services is copyright (c) 1996-2009 Andrew Church. |
4 |
* E-mail: <achurch@achurch.org> |
5 |
* Parts written by Andrew Kempe and others. |
6 |
* This program is free but copyrighted software; see the file GPL.txt for |
7 |
* details. |
8 |
*/ |
9 |
|
10 |
#include "services.h" |
11 |
#include "modules.h" |
12 |
#include "language.h" |
13 |
#include "timeout.h" |
14 |
#include "modules/nickserv/nickserv.h" |
15 |
#include "modules/operserv/operserv.h" |
16 |
|
17 |
#include "chanserv.h" |
18 |
#include "cs-local.h" |
19 |
|
20 |
/*************************************************************************/ |
21 |
|
22 |
static int cb_check_modes = -1; |
23 |
static int cb_check_chan_user_modes = -1; |
24 |
static int cb_check_kick = -1; |
25 |
|
26 |
/*************************************************************************/ |
27 |
/*************************************************************************/ |
28 |
|
29 |
/* Check the current modes on a channel; if they conflict with a mode lock, |
30 |
* fix them. */ |
31 |
|
32 |
void check_modes(Channel *c) |
33 |
{ |
34 |
static int in_check_modes = 0; |
35 |
ChannelInfo *ci; |
36 |
char newmode[3]; |
37 |
int flag; |
38 |
|
39 |
if (!c || c->bouncy_modes) |
40 |
return; |
41 |
|
42 |
if (!NoBouncyModes) { |
43 |
/* Check for mode bouncing */ |
44 |
if (c->server_modecount >= 3 && c->chanserv_modecount >= 3) { |
45 |
wallops(NULL, "Warning: unable to set modes on channel %s. " |
46 |
"Are your servers configured correctly?", c->name); |
47 |
module_log("Bouncy modes on channel %s", c->name); |
48 |
c->bouncy_modes = 1; |
49 |
return; |
50 |
} |
51 |
if (c->chanserv_modetime != time(NULL)) { |
52 |
c->chanserv_modecount = 0; |
53 |
c->chanserv_modetime = time(NULL); |
54 |
} |
55 |
c->chanserv_modecount++; |
56 |
} |
57 |
|
58 |
ci = c->ci; |
59 |
if (!ci) { |
60 |
/* Services _always_ knows who should be +r. If a channel tries to be |
61 |
* +r and is not registered, send mode -r. This will compensate for |
62 |
* servers that are split when mode -r is initially sent and then try |
63 |
* to set +r when they rejoin. -TheShadow */ |
64 |
if (c->mode & chanmode_reg) { |
65 |
char buf[BUFSIZE]; |
66 |
snprintf(buf, sizeof(buf), "-%s", |
67 |
mode_flags_to_string(chanmode_reg, MODE_CHANNEL)); |
68 |
set_cmode(s_ChanServ, c, buf); |
69 |
/* Flush it out immediately. Note that this won't cause |
70 |
* infinite recursion because we're clearing the mode that |
71 |
* got us here in the first place. */ |
72 |
set_cmode(NULL, c); |
73 |
} |
74 |
return; |
75 |
} |
76 |
|
77 |
/* Avoid infinite recursion (recursion occurs if set_cmode() flushes |
78 |
* out mode changes in the middle of setting them here) */ |
79 |
if (in_check_modes) |
80 |
return; |
81 |
in_check_modes++; |
82 |
|
83 |
newmode[2] = 0; |
84 |
/* Note that MODE_INVALID == 1<<31, so we can just stop there */ |
85 |
for (flag = 1; flag != MODE_INVALID; flag <<= 1) { |
86 |
int add; |
87 |
if ((ci->mlock.on | chanmode_reg) & flag) |
88 |
add = 1; |
89 |
else if (ci->mlock.off & flag) |
90 |
add = 0; |
91 |
else |
92 |
continue; |
93 |
if (call_callback_4(cb_check_modes, c, ci, add, flag) > 0) { |
94 |
continue; |
95 |
} else if (flag == CMODE_k) { |
96 |
if (c->key && (!add || (add && c->key && ci->mlock.key |
97 |
&& strcmp(c->key, ci->mlock.key) != 0))) { |
98 |
set_cmode(s_ChanServ, c, "-k", c->key); |
99 |
set_cmode(NULL, c); /* flush it out */ |
100 |
} |
101 |
if (add && !c->key) |
102 |
set_cmode(s_ChanServ, c, "+k", ci->mlock.key); |
103 |
} else if (flag == CMODE_l) { |
104 |
if (add && ci->mlock.limit != c->limit) { |
105 |
char limitbuf[16]; |
106 |
snprintf(limitbuf, sizeof(limitbuf), "%d", ci->mlock.limit); |
107 |
set_cmode(s_ChanServ, c, "+l", limitbuf); |
108 |
} else if (!add && c->limit != 0) { |
109 |
set_cmode(s_ChanServ, c, "-l"); |
110 |
} |
111 |
} else if (add ^ !!(c->mode & flag)) { |
112 |
newmode[0] = add ? '+' : '-'; |
113 |
newmode[1] = mode_flag_to_char(flag, MODE_CHANNEL); |
114 |
set_cmode(s_ChanServ, c, newmode); |
115 |
} |
116 |
} |
117 |
|
118 |
in_check_modes--; |
119 |
} |
120 |
|
121 |
/*************************************************************************/ |
122 |
|
123 |
/* Check whether a user should be opped or voiced on a channel, and if so, |
124 |
* do it. Updates the channel's last used time if the user is opped. |
125 |
* `oldmodes' is the user's current mode set, or -1 if all modes should |
126 |
* be checked. `source' is the source of the message which caused the mode |
127 |
* change, NULL for a join (but see below). |
128 |
* |
129 |
* Note that this function may be called with an empty `source' (i.e., not |
130 |
* NULL, but the empty string) to force a recheck of the user's modes |
131 |
* without checking whether the mode changes should be permitted for the |
132 |
* particular source. |
133 |
*/ |
134 |
|
135 |
/* Local helper routine */ |
136 |
static void local_set_cumodes(Channel *c, char plusminus, int32 modes, |
137 |
struct c_userlist *cu); |
138 |
|
139 |
void check_chan_user_modes(const char *source, struct c_userlist *u, |
140 |
Channel *c, int32 oldmodes) |
141 |
{ |
142 |
User *user = u->user; |
143 |
ChannelInfo *ci = c->ci; |
144 |
int32 modes = u->mode; |
145 |
int is_servermode = (!source || strchr(source, '.') != NULL); |
146 |
int32 res; /* result from check_access_cumode() */ |
147 |
|
148 |
/* Don't change modes on unregistered or forbidden channels */ |
149 |
if (!ci || (ci->flags & CF_VERBOTEN)) |
150 |
return; |
151 |
|
152 |
/* Don't reverse mode changes made by Services (because we already |
153 |
* prevent people from doing improper mode changes via Services, so |
154 |
* anything that gets here must be okay). */ |
155 |
if (source && (irc_stricmp(source, ServerName) == 0 |
156 |
|| irc_stricmp(source, s_ChanServ) == 0 |
157 |
|| irc_stricmp(source, s_OperServ) == 0)) |
158 |
return; |
159 |
|
160 |
/* Also don't reverse mode changes by the user themselves, unless the |
161 |
* user is -o now (this could happen if we've sent out a -o already but |
162 |
* the user got in a +v or such before the -o reached their server), or |
163 |
* the user is going to be deopped soon but the -o is held up by |
164 |
* MergeChannelModes (the CUFLAG_DEOPPED flag). |
165 |
* |
166 |
* We don't do this check for IRC operators to accommodate servers |
167 |
* which allow opers to +o themselves on channels. We also allow -h |
168 |
* and +/-v by +h (halfop) users on halfop-supporting ircds, because |
169 |
* the ircd allows it. |
170 |
*/ |
171 |
if (source && !is_oper(user) && irc_stricmp(source, user->nick) == 0) { |
172 |
if (!(oldmodes & CUMODE_o) || (u->flags & CUFLAG_DEOPPED)) { |
173 |
int16 cumode_h = mode_char_to_flag('h',MODE_CHANUSER); |
174 |
if (!((oldmodes & cumode_h) |
175 |
&& !((oldmodes^modes) & ~(CUMODE_v|cumode_h))) |
176 |
) { |
177 |
local_set_cumodes(c, '-', (modes & ~oldmodes), u); |
178 |
} |
179 |
} |
180 |
return; |
181 |
} |
182 |
|
183 |
/* Check early for server auto-ops */ |
184 |
if ((modes & CUMODE_o) |
185 |
&& !(ci->flags & CF_LEAVEOPS) |
186 |
&& is_servermode |
187 |
) { |
188 |
if (!is_oper(user) /* Here, too, don't subtract modes from opers */ |
189 |
&& (time(NULL)-start_time >= CSRestrictDelay |
190 |
|| !check_access_if_idented(user, ci, CA_AUTOOP)) |
191 |
&& !check_access(user, ci, CA_AUTOOP) |
192 |
) { |
193 |
notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ); |
194 |
u->flags |= CUFLAG_DEOPPED; |
195 |
set_cmode(s_ChanServ, c, "-o", user->nick); |
196 |
modes &= ~CUMODE_o; |
197 |
} else if (check_access(user, ci, CA_AUTOOP)) { |
198 |
/* The user's an autoop user; update the last-used time here, |
199 |
* because it won't get updated below (they're already opped) */ |
200 |
ci->last_used = time(NULL); |
201 |
} |
202 |
} |
203 |
|
204 |
/* Let the protocol module have a hack at it */ |
205 |
if (call_callback_4(cb_check_chan_user_modes, source, user, c, modes) > 0) |
206 |
return; |
207 |
|
208 |
/* Adjust modes based on channel access */ |
209 |
if (oldmodes < 0) { |
210 |
res = check_access_cumode(user, ci, modes, ~0); |
211 |
} else { |
212 |
int32 changed = modes ^ oldmodes; |
213 |
res = check_access_cumode(user, ci, changed & modes, changed); |
214 |
} |
215 |
|
216 |
/* Check for mode additions. Only check if join or server mode change, |
217 |
* unless ENFORCE is set */ |
218 |
/* Note: modes to add = changed modes & off new-modes = res & ~modes */ |
219 |
if ((res & ~modes) |
220 |
&& (oldmodes < 0 || is_servermode || (ci->flags & CF_ENFORCE)) |
221 |
) { |
222 |
local_set_cumodes(c, '+', res & ~modes, u); |
223 |
if ((res & ~modes) & CUMODE_o) |
224 |
ci->last_used = time(NULL); |
225 |
} |
226 |
|
227 |
/* Don't subtract modes from opers */ |
228 |
if (is_oper(user)) |
229 |
return; |
230 |
|
231 |
/* Check for mode subtractions */ |
232 |
if (res & modes) |
233 |
local_set_cumodes(c, '-', res & modes, u); |
234 |
} |
235 |
|
236 |
/************************************/ |
237 |
|
238 |
/* Helper routine for check_chan_user_modes(): sets all of the given modes |
239 |
* on client `cu' in channel `c'. |
240 |
*/ |
241 |
|
242 |
static void local_set_cumodes(Channel *c, char plusminus, int32 modes, |
243 |
struct c_userlist *cu) |
244 |
{ |
245 |
char buf[3], modestr[BUFSIZE], *s; |
246 |
|
247 |
buf[0] = plusminus; |
248 |
buf[2] = 0; |
249 |
strbcpy(modestr, mode_flags_to_string(modes, MODE_CHANUSER)); |
250 |
s = modestr; |
251 |
while (*s) { |
252 |
buf[1] = *s++; |
253 |
set_cmode(s_ChanServ, c, buf, cu->user->nick); |
254 |
} |
255 |
/* Set user's modes now, so check_chan_user_modes() can properly |
256 |
* determine whether subsequent modes should be set or not */ |
257 |
if (plusminus == '+') |
258 |
cu->mode |= modes; |
259 |
else if (plusminus == '-') |
260 |
cu->mode &= ~modes; |
261 |
} |
262 |
|
263 |
/*************************************************************************/ |
264 |
|
265 |
/* List of channels currently inhabited */ |
266 |
typedef struct csinhabitdata_ CSInhabitData; |
267 |
struct csinhabitdata_ { |
268 |
CSInhabitData *next, *prev; |
269 |
char chan[CHANMAX]; |
270 |
Timeout *to; |
271 |
}; |
272 |
static CSInhabitData *inhabit_list = NULL; |
273 |
|
274 |
|
275 |
/* Tiny helper routine to get ChanServ out of a channel after it went in. */ |
276 |
static void timeout_leave(Timeout *to) |
277 |
{ |
278 |
CSInhabitData *data = to->data; |
279 |
send_cmd(s_ChanServ, "PART %s", data->chan); |
280 |
LIST_REMOVE(data, inhabit_list); |
281 |
free(data); |
282 |
} |
283 |
|
284 |
|
285 |
/* Check whether a user is permitted to be on a channel. If so, return 0; |
286 |
* else, kickban the user with an appropriate message (could be either |
287 |
* AKICK or restricted access) and return 1. When `on_join' is nonzero, |
288 |
* this routine will _not_ call do_kick(), assuming that the user is not |
289 |
* yet on the internal channel list. |
290 |
*/ |
291 |
|
292 |
int check_kick(User *user, const char *chan, int on_join) |
293 |
{ |
294 |
Channel *c = get_channel(chan); |
295 |
ChannelInfo *ci = get_channelinfo(chan); |
296 |
int i; |
297 |
char *mask, *s; |
298 |
const char *reason; |
299 |
char reasonbuf[BUFSIZE]; |
300 |
int stay; |
301 |
|
302 |
|
303 |
if (CSForbidShortChannel && strcmp(chan, "#") == 0) { |
304 |
mask = sstrdup("*!*@*"); |
305 |
reason = getstring(user->ngi, CHAN_MAY_NOT_BE_USED); |
306 |
goto kick; |
307 |
} |
308 |
|
309 |
if (is_services_admin(user)) { |
310 |
put_channelinfo(ci); |
311 |
return 0; |
312 |
} |
313 |
|
314 |
i = call_callback_5(cb_check_kick, user, chan, ci, &mask, &reason); |
315 |
if (i == 2) { |
316 |
put_channelinfo(ci); |
317 |
return 0; |
318 |
} else if (i == 1) { |
319 |
goto kick; |
320 |
} |
321 |
|
322 |
/* Nothing else here affects IRC operators */ |
323 |
if (is_oper(user)) { |
324 |
put_channelinfo(ci); |
325 |
return 0; |
326 |
} |
327 |
|
328 |
/* Check join against channel's modes--this is properly the domain of |
329 |
* the IRC server, but you never know... */ |
330 |
if (c) { |
331 |
if (c->mode & chanmode_opersonly) { |
332 |
/* We know from above that they're not an oper */ |
333 |
mask = create_mask(user, 1); |
334 |
reason = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN); |
335 |
goto kick; |
336 |
} |
337 |
} |
338 |
|
339 |
if (!ci) { |
340 |
if (CSRegisteredOnly) { |
341 |
mask = sstrdup("*!*@*"); |
342 |
reason = getstring(user->ngi, CHAN_MAY_NOT_BE_USED); |
343 |
goto kick; |
344 |
} else { |
345 |
put_channelinfo(ci); |
346 |
return 0; |
347 |
} |
348 |
} |
349 |
|
350 |
if (ci->flags & (CF_VERBOTEN | CF_SUSPENDED)) { |
351 |
mask = sstrdup("*!*@*"); |
352 |
reason = getstring(user->ngi, CHAN_MAY_NOT_BE_USED); |
353 |
goto kick; |
354 |
} |
355 |
|
356 |
if (ci->mlock.on & chanmode_opersonly) { |
357 |
/* We already know they're not an oper, so kick them off */ |
358 |
mask = create_mask(user, 1); |
359 |
reason = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN); |
360 |
goto kick; |
361 |
} |
362 |
|
363 |
if (!CSSkipModeRCheck && (ci->mlock.on & chanmode_regonly) |
364 |
&& !user_identified(user) |
365 |
) { |
366 |
/* User must have usermode_reg flags, i.e. be using a registered |
367 |
* nick and have identified, in order to join a chanmode_regonly |
368 |
* channel */ |
369 |
mask = create_mask(user, 1); |
370 |
reason = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN); |
371 |
goto kick; |
372 |
} |
373 |
|
374 |
ARRAY_FOREACH (i, ci->akick) { |
375 |
if (!ci->akick[i].mask) { |
376 |
log_debug(1, "%s autokick %d has NULL mask, deleting", ci->name,i); |
377 |
ARRAY_REMOVE(ci->akick, i); |
378 |
i--; |
379 |
continue; |
380 |
} |
381 |
if (match_usermask(ci->akick[i].mask, user)) { |
382 |
module_log_debug(2, "%s matched akick %s", |
383 |
user->nick, ci->akick[i].mask); |
384 |
mask = sstrdup(ci->akick[i].mask); |
385 |
snprintf(reasonbuf, sizeof(reasonbuf), "AKICK by %s%s%s%s", |
386 |
ci->akick[i].who, |
387 |
ci->akick[i].reason ? " (" : "", |
388 |
ci->akick[i].reason ? ci->akick[i].reason : "", |
389 |
ci->akick[i].reason ? ")" : ""); |
390 |
reason = reasonbuf; |
391 |
time(&ci->akick[i].lastused); |
392 |
goto kick; |
393 |
} |
394 |
} |
395 |
|
396 |
if ((time(NULL)-start_time >= CSRestrictDelay |
397 |
|| check_access_if_idented(user, ci, CA_NOJOIN)) |
398 |
&& check_access(user, ci, CA_NOJOIN) |
399 |
) { |
400 |
mask = create_mask(user, 1); |
401 |
reason = getstring(user->ngi, CHAN_NOT_ALLOWED_TO_JOIN); |
402 |
goto kick; |
403 |
} |
404 |
|
405 |
put_channelinfo(ci); |
406 |
return 0; |
407 |
|
408 |
kick: |
409 |
module_log_debug(1, "check_kick() kicking %s!%s@%s from %s", |
410 |
user->nick, user->username, user->host, chan); |
411 |
/* When called on join, the user has not been added to our channel user |
412 |
* list yet, so we check whether the channel does not exist rather than |
413 |
* whether the channel has only one user in it. When called from AKICK |
414 |
* ENFORCE, the user _will_ be in the list, so we need to check whether |
415 |
* the list contains only this user. Since neither condition can cause |
416 |
* a false positive, we just check both and do a logical-or on the |
417 |
* results. */ |
418 |
stay = (c == NULL) || (c->users->user == user && c->users->next == NULL); |
419 |
if (stay) { |
420 |
CSInhabitData *data; |
421 |
/* Only enter the channel if we're not already in it */ |
422 |
LIST_SEARCH(inhabit_list, chan, chan, irc_stricmp, data); |
423 |
if (!data) { |
424 |
Timeout *to; |
425 |
send_cmd(s_ChanServ, "JOIN %s", chan); |
426 |
to = add_timeout(CSInhabit, timeout_leave, 0); |
427 |
to->data = data = smalloc(sizeof(*data)); |
428 |
LIST_INSERT(data, inhabit_list); |
429 |
strbcpy(data->chan, chan); |
430 |
data->to = to; |
431 |
} |
432 |
} |
433 |
/* Make sure the mask has a ! in it */ |
434 |
if (!(s = strchr(mask, '!')) || s > strchr(mask, '@')) { |
435 |
int len = strlen(mask); |
436 |
mask = srealloc(mask, len+3); |
437 |
memmove(mask+2, mask, len+1); |
438 |
mask[0] = '*'; |
439 |
mask[1] = '!'; |
440 |
} |
441 |
/* Clear any exceptions matching the user, to ensure that the ban takes |
442 |
* effect */ |
443 |
if (c) |
444 |
clear_channel(c, CLEAR_EXCEPTS, user); |
445 |
/* Apparently invites can get around bans, so check for ban before adding*/ |
446 |
if (!chan_has_ban(chan, mask)) { |
447 |
send_cmode_cmd(s_ChanServ, chan, "+b %s", mask); |
448 |
if (c) { |
449 |
char *av[3]; |
450 |
av[0] = (char *)chan; |
451 |
av[1] = (char *)"+b"; |
452 |
av[2] = mask; |
453 |
do_cmode(s_ChanServ, 3, av); |
454 |
} |
455 |
} |
456 |
free(mask); |
457 |
send_channel_cmd(s_ChanServ, "KICK %s %s :%s", chan, user->nick, reason); |
458 |
if (!on_join) { |
459 |
/* The user is already in the channel userlist, so get them out */ |
460 |
char *av[3]; |
461 |
av[0] = (char *)chan; |
462 |
av[1] = user->nick; |
463 |
av[2] = (char *)"check_kick"; /* dummy value */ |
464 |
do_kick(s_ChanServ, 3, av); |
465 |
} |
466 |
put_channelinfo(ci); |
467 |
return 1; |
468 |
} |
469 |
|
470 |
/*************************************************************************/ |
471 |
|
472 |
/* See if the topic is locked on the given channel, and return 1 (and fix |
473 |
* the topic) if so, 0 if not. */ |
474 |
|
475 |
int check_topiclock(Channel *c, time_t topic_time) |
476 |
{ |
477 |
ChannelInfo *ci = c->ci; |
478 |
|
479 |
if (!ci || !(ci->flags & CF_TOPICLOCK)) |
480 |
return 0; |
481 |
c->topic_time = topic_time; /* because set_topic() may need it */ |
482 |
set_topic(s_ChanServ, c, ci->last_topic, |
483 |
*ci->last_topic_setter ? ci->last_topic_setter : s_ChanServ, |
484 |
ci->last_topic_time); |
485 |
return 1; |
486 |
} |
487 |
|
488 |
/*************************************************************************/ |
489 |
/*************************************************************************/ |
490 |
|
491 |
int init_check(void) |
492 |
{ |
493 |
cb_check_modes = register_callback("check_modes"); |
494 |
cb_check_chan_user_modes = register_callback("check_chan_user_modes"); |
495 |
cb_check_kick = register_callback("check_kick"); |
496 |
if (cb_check_modes < 0 || cb_check_chan_user_modes < 0 |
497 |
|| cb_check_kick < 0 |
498 |
) { |
499 |
module_log("check: Unable to register callbacks"); |
500 |
exit_check(); |
501 |
return 0; |
502 |
} |
503 |
return 1; |
504 |
} |
505 |
|
506 |
/*************************************************************************/ |
507 |
|
508 |
void exit_check() |
509 |
{ |
510 |
CSInhabitData *inhabit, *tmp; |
511 |
|
512 |
LIST_FOREACH_SAFE (inhabit, inhabit_list, tmp) { |
513 |
del_timeout(inhabit->to); |
514 |
LIST_REMOVE(inhabit, inhabit_list); |
515 |
free(inhabit); |
516 |
} |
517 |
unregister_callback(cb_check_kick); |
518 |
unregister_callback(cb_check_chan_user_modes); |
519 |
unregister_callback(cb_check_modes); |
520 |
} |
521 |
|
522 |
/*************************************************************************/ |
523 |
|
524 |
/* |
525 |
* Local variables: |
526 |
* c-file-style: "stroustrup" |
527 |
* c-file-offsets: ((case-label . *) (statement-case-intro . *)) |
528 |
* indent-tabs-mode: nil |
529 |
* End: |
530 |
* |
531 |
* vim: expandtab shiftwidth=4: |
532 |
*/ |