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

File Contents

# Content
1 /* Channel-handling routines.
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
13 /*************************************************************************/
14
15 #define HASHFUNC(key) DEFAULT_HASHFUNC(key+1)
16 #define add_channel static add_channel
17 #define del_channel static del_channel
18 #include "hash.h"
19 DEFINE_HASH(channel, Channel, name)
20 #undef add_channel
21 #undef del_channel
22
23 static int cb_create = -1;
24 static int cb_delete = -1;
25 static int cb_join = -1;
26 static int cb_join_check = -1;
27 static int cb_mode = -1;
28 static int cb_mode_change = -1;
29 static int cb_umode_change = -1;
30 static int cb_topic = -1;
31
32 /*************************************************************************/
33 /*************************************************************************/
34
35 int channel_init(int ac, char **av)
36 {
37 cb_create = register_callback("channel create");
38 cb_delete = register_callback("channel delete");
39 cb_join = register_callback("channel JOIN");
40 cb_join_check = register_callback("channel JOIN check");
41 cb_mode = register_callback("channel MODE");
42 cb_mode_change = register_callback("channel mode change");
43 cb_umode_change = register_callback("channel umode change");
44 cb_topic = register_callback("channel TOPIC");
45 if (cb_create < 0 || cb_delete < 0 || cb_join < 0 || cb_join_check < 0
46 || cb_mode < 0 || cb_mode_change < 0 || cb_umode_change < 0
47 || cb_topic < 0
48 ) {
49 log("channel_init: register_callback() failed\n");
50 return 0;
51 }
52 return 1;
53 }
54
55 /*************************************************************************/
56
57 void channel_cleanup(void)
58 {
59 Channel *c;
60
61 for (c = first_channel(); c; c = next_channel())
62 del_channel(c);
63 unregister_callback(cb_topic);
64 unregister_callback(cb_umode_change);
65 unregister_callback(cb_mode_change);
66 unregister_callback(cb_mode);
67 unregister_callback(cb_join_check);
68 unregister_callback(cb_join);
69 unregister_callback(cb_delete);
70 unregister_callback(cb_create);
71 }
72
73 /*************************************************************************/
74
75 /* Return statistics. Pointers are assumed to be valid. */
76
77 void get_channel_stats(long *nrec, long *memuse)
78 {
79 long count = 0, mem = 0;
80 Channel *chan;
81 struct c_userlist *cu;
82 int i;
83
84 for (chan = first_channel(); chan; chan = next_channel()) {
85 count++;
86 mem += sizeof(*chan);
87 if (chan->topic)
88 mem += strlen(chan->topic)+1;
89 if (chan->key)
90 mem += strlen(chan->key)+1;
91 ARRAY_FOREACH (i, chan->bans) {
92 mem += sizeof(char *);
93 if (chan->bans[i])
94 mem += strlen(chan->bans[i])+1;
95 }
96 ARRAY_FOREACH (i, chan->excepts) {
97 mem += sizeof(char *);
98 if (chan->excepts[i])
99 mem += strlen(chan->excepts[i])+1;
100 }
101 LIST_FOREACH (cu, chan->users)
102 mem += sizeof(*cu);
103 }
104 *nrec = count;
105 *memuse = mem;
106 }
107
108 /*************************************************************************/
109 /*************************************************************************/
110
111 /* Add/remove a user to/from a channel, creating or deleting the channel as
112 * necessary. If creating the channel, restore mode lock and topic as
113 * necessary. Also check for auto-opping and auto-voicing. If a mode is
114 * given, it is assumed to have been set by the remote server.
115 * Returns the Channel structure for the given channel, or NULL if the user
116 * was refused access to the channel (by the join check callback).
117 */
118
119 Channel *chan_adduser(User *user, const char *chan, int32 modes)
120 {
121 Channel *c = get_channel(chan);
122 int newchan = !c;
123 struct c_userlist *u;
124
125 if (call_callback_2(cb_join_check, chan, user) > 0)
126 return NULL;
127 if (newchan) {
128 log_debug(1, "Creating channel %s", chan);
129 /* Allocate pre-cleared memory */
130 c = scalloc(sizeof(Channel), 1);
131 strbcpy(c->name, chan);
132 c->creation_time = time(NULL);
133 add_channel(c);
134 call_callback_3(cb_create, c, user, modes);
135 }
136 u = smalloc(sizeof(struct c_userlist));
137 LIST_INSERT(u, c->users);
138 u->user = user;
139 u->mode = modes;
140 u->flags = 0;
141 call_callback_2(cb_join, c, u);
142 return c;
143 }
144
145
146 void chan_deluser(User *user, Channel *c)
147 {
148 struct c_userlist *u;
149 int i;
150
151 LIST_SEARCH_SCALAR(c->users, user, user, u);
152 if (!u) {
153 log("channel: BUG: chan_deluser() called for %s in %s but they "
154 "were not found on the channel's userlist.",
155 user->nick, c->name);
156 return;
157 }
158 LIST_REMOVE(u, c->users);
159 free(u);
160
161 if (!c->users) {
162 log_debug(1, "Deleting channel %s", c->name);
163 call_callback_1(cb_delete, c);
164 set_cmode(NULL, c); /* make sure nothing's left buffered */
165 free(c->topic);
166 free(c->key);
167 free(c->link);
168 free(c->flood);
169 for (i = 0; i < c->bans_count; i++)
170 free(c->bans[i]);
171 free(c->bans);
172 for (i = 0; i < c->excepts_count; i++)
173 free(c->excepts[i]);
174 free(c->excepts);
175 del_channel(c);
176 free(c);
177 }
178 }
179
180 /*************************************************************************/
181
182 /* Search for the given ban on the given channel, and return the index into
183 * chan->bans[] if found, -1 otherwise. Nicknames are compared using
184 * irc_stricmp(), usernames and hostnames using stricmp().
185 */
186 static int find_ban(const Channel *chan, const char *ban)
187 {
188 char *s, *t;
189 int i;
190
191 t = strchr(ban, '!');
192 i = 0;
193 ARRAY_FOREACH (i, chan->bans) {
194 s = strchr(chan->bans[i], '!');
195 if (s && t) {
196 if (s-(chan->bans[i]) == t-ban
197 && irc_strnicmp(chan->bans[i], ban, s-(chan->bans[i])) == 0
198 && stricmp(s+1, t+1) == 0
199 ) {
200 return i;
201 }
202 } else if (!s && !t) {
203 /* Bans without '!' should be impossible; just
204 * do a case-insensitive compare */
205 if (stricmp(chan->bans[i], ban) == 0)
206 return i;
207 }
208 }
209 return -1;
210 }
211
212 /*************************************************************************/
213
214 /* Search for the given ban (case-insensitive) on the channel; return 1 if
215 * it exists, 0 if not.
216 */
217
218 int chan_has_ban(const char *chan, const char *ban)
219 {
220 Channel *c = get_channel(chan);
221 if (!c)
222 return 0;
223 return find_ban(c, ban) >= 0;
224 }
225
226 /*************************************************************************/
227
228 /* Handle a channel MODE command.
229 * When called internally to modify channel modes, callers may assume that
230 * the contents of the argument strings will not be modified.
231 */
232
233 /* Hack to allow -o+v to work without having to search the whole channel
234 * user list for changed modes. */
235 #define MAX_CUMODES 16
236 static struct {
237 struct c_userlist *user;
238 int32 add, remove;
239 } cumode_changes[MAX_CUMODES];
240 static int cumode_count = 0;
241
242 static void do_cumode(const char *source, Channel *chan, int32 flag, int add,
243 const char *nick);
244 static void finish_cumode(const char *source, Channel *chan);
245
246 void do_cmode(const char *source, int ac, char **av)
247 {
248 Channel *chan;
249 char *s;
250 int add = 1; /* 1 if adding modes, 0 if deleting */
251 char *modestr;
252
253 chan = get_channel(av[0]);
254 if (!chan) {
255 log_debug(1, "channel: MODE %s for nonexistent channel %s",
256 merge_args(ac-1, av+1), av[0]);
257 return;
258 }
259 if ((protocol_features & PF_MODETS_FIRST) && isdigit(av[1][0])) {
260 ac--;
261 av++;
262 }
263 modestr = av[1];
264
265 if (!NoBouncyModes) {
266 /* Count identical server mode changes per second (mode bounce check)*/
267 /* Doesn't trigger on +/-[bov] or other multiply-settable modes */
268 static char multimodes[BUFSIZE];
269 if (!*multimodes) {
270 int i = 0;
271 i = snprintf(multimodes, sizeof(multimodes), "%s",
272 chanmode_multiple);
273 snprintf(multimodes+i, sizeof(multimodes)-i, "%s",
274 mode_flags_to_string(MODE_ALL,MODE_CHANUSER));
275 }
276 if (strchr(source, '.') && strcmp(source, ServerName) != 0
277 && !modestr[strcspn(modestr, multimodes)]
278 ) {
279 static char lastmodes[BUFSIZE];
280 if (time(NULL) != chan->server_modetime
281 || strcmp(modestr, lastmodes) != 0
282 ) {
283 chan->server_modecount = 0;
284 chan->server_modetime = time(NULL);
285 strbcpy(lastmodes, modestr);
286 }
287 chan->server_modecount++;
288 }
289 }
290
291 s = modestr;
292 ac -= 2;
293 av += 2;
294 cumode_count = 0;
295
296 while (*s) {
297 char modechar = *s++;
298 int32 flag;
299 int params;
300
301 if (modechar == '+') {
302 add = 1;
303 continue;
304 } else if (modechar == '-') {
305 add = 0;
306 continue;
307 } else if (add < 0) {
308 continue;
309 }
310
311 /* Check for it as a channel user mode */
312
313 flag = mode_char_to_flag(modechar, MODE_CHANUSER);
314 if (flag) {
315 if (--ac < 0) {
316 log("channel: MODE %s %s: missing parameter for %c%c",
317 chan->name, modestr, add ? '+' : '-', modechar);
318 break;
319 }
320 do_cumode(source, chan, flag, add, *av++);
321 continue;
322 }
323
324 /* Nope, must be a regular channel mode */
325
326 flag = mode_char_to_flag(modechar, MODE_CHANNEL);
327 if (!flag)
328 continue;
329 if (flag == MODE_INVALID)
330 flag = 0;
331 params = mode_char_to_params(modechar, MODE_CHANNEL);
332 params = (params >> (add*8)) & 0xFF;
333 if (ac < params) {
334 log("channel: MODE %s %s: missing parameter(s) for %c%c",
335 chan->name, modestr, add ? '+' : '-', modechar);
336 break;
337 }
338
339 if (call_callback_5(cb_mode, source, chan, modechar, add, av) <= 0) {
340
341 if (add)
342 chan->mode |= flag;
343 else
344 chan->mode &= ~flag;
345
346 switch (modechar) {
347 case 'k':
348 free(chan->key);
349 if (add)
350 chan->key = sstrdup(av[0]);
351 else
352 chan->key = NULL;
353 break;
354
355 case 'l':
356 if (add)
357 chan->limit = atoi(av[0]);
358 else
359 chan->limit = 0;
360 break;
361
362 case 'b': {
363 int i = find_ban(chan, av[0]);
364 if (add) {
365 if (i < 0) { /* Don't add if it already exists */
366 ARRAY_EXTEND(chan->bans);
367 chan->bans[chan->bans_count-1] = sstrdup(av[0]);
368 }
369 } else {
370 if (i >= 0) {
371 free(chan->bans[i]);
372 ARRAY_REMOVE(chan->bans, i);
373 } else {
374 log("channel: MODE %s -b %s: ban not found",
375 chan->name, *av);
376 }
377 }
378 break;
379 } /* case 'b' */
380
381 } /* switch (modechar) */
382
383 } /* if (callback() <= 0) */
384
385 ac -= params;
386 av += params;
387
388 } /* while (*s) */
389
390 call_callback_2(cb_mode_change, source, chan);
391 finish_cumode(source, chan);
392 }
393
394 /* Modify a user's CUMODE. */
395 static void do_cumode(const char *source, Channel *chan, int32 flag, int add,
396 const char *nick)
397 {
398 struct c_userlist *u;
399 User *user;
400 int i;
401
402 user = get_user(nick);
403 if (!user) {
404 log_debug(1, "channel: MODE %s %c%c for nonexistent user %s",
405 chan->name, add ? '+' : '-',
406 mode_flag_to_char(flag, MODE_CHANUSER), nick);
407 return;
408 }
409 LIST_SEARCH_SCALAR(chan->users, user, user, u);
410 if (!u) {
411 log("channel: MODE %s %c%c for user %s not on channel",
412 chan->name, add ? '+' : '-',
413 mode_flag_to_char(flag, MODE_CHANUSER), nick);
414 return;
415 }
416
417 if (flag == MODE_INVALID)
418 return;
419 for (i = 0; i < cumode_count; i++) {
420 if (cumode_changes[i].user == u)
421 break;
422 }
423 if (i >= MAX_CUMODES) {
424 finish_cumode(source, chan);
425 i = cumode_count = 0;
426 }
427 cumode_changes[i].user = u;
428 if (i >= cumode_count) {
429 cumode_changes[i].add = cumode_changes[i].remove = 0;
430 }
431 if (add) {
432 cumode_changes[i].add |= flag;
433 cumode_changes[i].remove &= ~flag;
434 } else {
435 cumode_changes[i].remove |= flag;
436 cumode_changes[i].add &= ~flag;
437 }
438 if (i >= cumode_count)
439 cumode_count = i+1;
440 }
441
442 static void finish_cumode(const char *source, Channel *chan)
443 {
444 int i;
445
446 for (i = 0; i < cumode_count; i++) {
447 struct c_userlist *u = cumode_changes[i].user;
448 int32 oldmode = u->mode;
449 u->mode |= cumode_changes[i].add;
450 u->mode &= ~cumode_changes[i].remove;
451 if (u->mode != oldmode)
452 call_callback_4(cb_umode_change, source, chan, u, oldmode);
453 }
454 cumode_count = 0;
455 }
456
457 /*************************************************************************/
458
459 /* Handle a TOPIC command. */
460
461 void do_topic(const char *source, int ac, char **av)
462 {
463 Channel *c = get_channel(av[0]);
464 const char *topic;
465 char *s;
466
467 if (!c) {
468 log_debug(1, "channel: TOPIC %s for nonexistent channel %s",
469 merge_args(ac-1, av+1), av[0]);
470 return;
471 }
472 s = strchr(av[1], '!');
473 if (s)
474 *s = 0;
475 if (ac > 3)
476 topic = av[3];
477 else
478 topic = "";
479 if (call_callback_4(cb_topic, c, topic, av[1], strtotime(av[2],NULL)) > 0)
480 return;
481 strbcpy(c->topic_setter, av[1]);
482 if (c->topic) {
483 free(c->topic);
484 c->topic = NULL;
485 }
486 if (ac > 3 && *av[3])
487 c->topic = sstrdup(av[3]);
488 }
489
490 /*************************************************************************/
491
492 /*
493 * Local variables:
494 * c-file-style: "stroustrup"
495 * c-file-offsets: ((case-label . *) (statement-case-intro . *))
496 * indent-tabs-mode: nil
497 * End:
498 *
499 * vim: expandtab shiftwidth=4:
500 */