ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/vendor/ircservices-5.1.24/modules/chanserv/check.c
Revision: 3389
Committed: Fri Apr 25 14:12:15 2014 UTC (11 years, 4 months ago) by michael
Content type: text/x-csrc
File size: 18159 byte(s)
Log Message:
- Imported ircservices-5.1.24

File Contents

# Content
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 */