1 |
/* Various routines to perform simple actions. |
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 "language.h" |
12 |
#include "modules.h" |
13 |
#include "timeout.h" |
14 |
|
15 |
/*************************************************************************/ |
16 |
|
17 |
static int cb_clear_channel = -1; |
18 |
static int cb_set_topic = -1; |
19 |
|
20 |
/* Sender to be used with clear_channel() (empty string: use server name) */ |
21 |
static char clear_channel_sender[NICKMAX] = {0}; |
22 |
|
23 |
/*************************************************************************/ |
24 |
/*************************************************************************/ |
25 |
|
26 |
int actions_init(int ac, char **av) |
27 |
{ |
28 |
cb_clear_channel = register_callback("clear channel"); |
29 |
cb_set_topic = register_callback("set topic"); |
30 |
if (cb_clear_channel < 0 || cb_set_topic < 0) { |
31 |
log("actions_init: register_callback() failed\n"); |
32 |
return 0; |
33 |
} |
34 |
return 1; |
35 |
} |
36 |
|
37 |
/*************************************************************************/ |
38 |
|
39 |
void actions_cleanup(void) |
40 |
{ |
41 |
unregister_callback(cb_set_topic); |
42 |
unregister_callback(cb_clear_channel); |
43 |
} |
44 |
|
45 |
/*************************************************************************/ |
46 |
/*************************************************************************/ |
47 |
|
48 |
/* Note a bad password attempt for the given user from the given service. |
49 |
* If they've used up their limit, toss them off. `service' is used only |
50 |
* for the sender of the bad-password and warning messages; these messages |
51 |
* are not sent if `service' is NULL. `what' describes what the password |
52 |
* was for, and is used in the kill message if the user is killed. The |
53 |
* function's return value is 1 if the user was warned, 2 if the user was |
54 |
* killed, and 0 otherwise. |
55 |
*/ |
56 |
|
57 |
int bad_password(const char *service, User *u, const char *what) |
58 |
{ |
59 |
time_t now = time(NULL); |
60 |
|
61 |
if (service) |
62 |
notice_lang(service, u, PASSWORD_INCORRECT); |
63 |
|
64 |
if (!BadPassLimit) |
65 |
return 0; |
66 |
|
67 |
if (BadPassTimeout > 0 && u->bad_pw_time > 0 |
68 |
&& now >= u->bad_pw_time + BadPassTimeout) |
69 |
u->bad_pw_count = 0; |
70 |
u->bad_pw_count++; |
71 |
u->bad_pw_time = now; |
72 |
if (u->bad_pw_count >= BadPassLimit) { |
73 |
char buf[BUFSIZE]; |
74 |
snprintf(buf, sizeof(buf), "Too many invalid passwords (%s)", what); |
75 |
kill_user(NULL, u->nick, buf); |
76 |
return 2; |
77 |
} else if (u->bad_pw_count == BadPassLimit-1) { |
78 |
if (service) |
79 |
notice_lang(service, u, PASSWORD_WARNING); |
80 |
return 1; |
81 |
} |
82 |
return 0; |
83 |
} |
84 |
|
85 |
/*************************************************************************/ |
86 |
|
87 |
/* Clear modes/users from a channel. The "what" parameter is one or more |
88 |
* of the CLEAR_* constants defined in services.h. "param" is: |
89 |
* - for CLEAR_USERS, a kick message (const char *) |
90 |
* - for CLEAR_UMODES, a bitmask of modes to clear (int32) |
91 |
* - for CLEAR_BANS and CLEAR_EXCEPTS, a User * to match against, or |
92 |
* NULL for all bans/exceptions |
93 |
* Note that CLEAR_EXCEPTS must be handled via callback for protocols which |
94 |
* support it. |
95 |
*/ |
96 |
|
97 |
static void clear_modes(const char *sender, Channel *chan); |
98 |
static void clear_bans(const char *sender, Channel *chan, User *u); |
99 |
static void clear_umodes(const char *sender, Channel *chan, int32 modes); |
100 |
static void clear_users(const char *sender, Channel *chan, const char *reason); |
101 |
|
102 |
void clear_channel(Channel *chan, int what, const void *param) |
103 |
{ |
104 |
const char *sender = |
105 |
*clear_channel_sender ? clear_channel_sender : ServerName; |
106 |
|
107 |
if (call_callback_4(cb_clear_channel, sender, chan, what, param) > 0) { |
108 |
set_cmode(NULL, chan); |
109 |
return; |
110 |
} |
111 |
|
112 |
if (what & CLEAR_USERS) { |
113 |
clear_users(sender, chan, (const char *)param); |
114 |
/* Once we kick all the users, nothing else will matter */ |
115 |
return; |
116 |
} |
117 |
|
118 |
if (what & CLEAR_MODES) |
119 |
clear_modes(sender, chan); |
120 |
if (what & CLEAR_BANS) |
121 |
clear_bans(sender, chan, (User *)param); |
122 |
if (what & CLEAR_UMODES) |
123 |
clear_umodes(sender, chan, (int32)(long)param); |
124 |
set_cmode(NULL, chan); /* Flush modes out */ |
125 |
} |
126 |
|
127 |
static void clear_modes(const char *sender, Channel *chan) |
128 |
{ |
129 |
char buf[BUFSIZE]; |
130 |
snprintf(buf, sizeof(buf), "-%s", |
131 |
mode_flags_to_string(chan->mode & ~chanmode_reg, MODE_CHANNEL)); |
132 |
set_cmode(sender, chan, buf, chan->key); |
133 |
} |
134 |
|
135 |
static void clear_bans(const char *sender, Channel *chan, User *u) |
136 |
{ |
137 |
int i, count; |
138 |
char **bans; |
139 |
|
140 |
if (!chan->bans_count) |
141 |
return; |
142 |
|
143 |
/* Save original ban info */ |
144 |
count = chan->bans_count; |
145 |
bans = smalloc(sizeof(char *) * count); |
146 |
memcpy(bans, chan->bans, sizeof(char *) * count); |
147 |
|
148 |
for (i = 0; i < count; i++) { |
149 |
if (!u || match_usermask(bans[i], u)) |
150 |
set_cmode(sender, chan, "-b", bans[i]); |
151 |
} |
152 |
free(bans); |
153 |
} |
154 |
|
155 |
static void clear_umodes(const char *sender, Channel *chan, int32 modes) |
156 |
{ |
157 |
struct c_userlist *cu; |
158 |
|
159 |
LIST_FOREACH (cu, chan->users) { |
160 |
int32 to_clear = cu->mode & modes; /* modes we need to clear */ |
161 |
int32 flag = 1; /* mode we're clearing now */ |
162 |
while (to_clear) { |
163 |
if (flag == MODE_INVALID) { |
164 |
log("BUG: hit invalid flag in clear_umodes!" |
165 |
" modes to clear = %08X, user modes = %08X", |
166 |
to_clear, cu->mode); |
167 |
break; |
168 |
} |
169 |
if (to_clear & flag) { |
170 |
char buf[3] = "-x"; |
171 |
buf[1] = mode_flag_to_char(flag, MODE_CHANUSER); |
172 |
set_cmode(sender, chan, buf, cu->user->nick); |
173 |
to_clear &= ~flag; |
174 |
} |
175 |
flag <<= 1; |
176 |
} |
177 |
cu->mode &= ~modes; |
178 |
} |
179 |
} |
180 |
|
181 |
static void clear_users(const char *sender, Channel *chan, const char *reason) |
182 |
{ |
183 |
char *av[3]; |
184 |
struct c_userlist *cu, *next; |
185 |
|
186 |
/* Prevent anyone from coming back in. The ban will disappear |
187 |
* once everyone's gone. */ |
188 |
set_cmode(sender, chan, "+b", "*!*@*"); |
189 |
set_cmode(NULL, chan); /* Flush modes out */ |
190 |
|
191 |
av[0] = chan->name; |
192 |
av[2] = (char *)reason; |
193 |
LIST_FOREACH_SAFE (cu, chan->users, next) { |
194 |
av[1] = cu->user->nick; |
195 |
send_channel_cmd(sender, "KICK %s %s :%s", |
196 |
av[0], av[1], av[2]); |
197 |
do_kick(sender, 3, av); |
198 |
} |
199 |
} |
200 |
|
201 |
/*************************************************************************/ |
202 |
|
203 |
/* Set the nickname to be used to send commands in clear_channel() calls. |
204 |
* If NULL, the server name is used; if PTR_INVALID, the name is not |
205 |
* changed. Returns the old value of the sender, or the empty string if no |
206 |
* nickname was set, in a static buffer. |
207 |
*/ |
208 |
|
209 |
const char *set_clear_channel_sender(const char *newsender) |
210 |
{ |
211 |
static char oldsender[NICKMAX]; |
212 |
strbcpy(oldsender, clear_channel_sender); |
213 |
if (newsender != PTR_INVALID) { |
214 |
if (newsender) { |
215 |
strbcpy(clear_channel_sender, newsender); |
216 |
} else { |
217 |
*clear_channel_sender = 0; |
218 |
} |
219 |
} |
220 |
return oldsender; |
221 |
} |
222 |
|
223 |
/*************************************************************************/ |
224 |
|
225 |
/* Remove a user from the IRC network. `source' is the nick which should |
226 |
* generate the kill, or NULL for a server-generated kill. |
227 |
*/ |
228 |
|
229 |
void kill_user(const char *source, const char *user, const char *reason) |
230 |
{ |
231 |
char *av[2]; |
232 |
char buf[BUFSIZE]; |
233 |
|
234 |
if (!user || !*user) |
235 |
return; |
236 |
if (!source || !*source) |
237 |
source = ServerName; |
238 |
if (!reason) |
239 |
reason = ""; |
240 |
snprintf(buf, sizeof(buf), "%s (%s)", source, reason); |
241 |
av[0] = (char *)user; |
242 |
av[1] = buf; |
243 |
send_cmd(source, "KILL %s :%s", user, av[1]); |
244 |
do_kill(source, 2, av); |
245 |
} |
246 |
|
247 |
/*************************************************************************/ |
248 |
|
249 |
/* Set the topic on a channel. `setter' must not be NULL. `source' is the |
250 |
* nick to use to send the TOPIC message; if NULL, the server name is used. |
251 |
*/ |
252 |
|
253 |
void set_topic(const char *source, Channel *c, const char *topic, |
254 |
const char *setter, time_t t) |
255 |
{ |
256 |
if (!source) |
257 |
source = ServerName; |
258 |
call_callback_5(cb_set_topic, source, c, topic, setter, t); |
259 |
free(c->topic); |
260 |
if (topic && *topic) |
261 |
c->topic = sstrdup(topic); |
262 |
else |
263 |
c->topic = NULL; |
264 |
strbcpy(c->topic_setter, setter); |
265 |
if (call_callback_5(cb_set_topic, source, c, NULL, NULL, t) > 0) |
266 |
return; |
267 |
} |
268 |
|
269 |
/*************************************************************************/ |
270 |
/*************************************************************************/ |
271 |
|
272 |
/* set_cmode(): Set modes for a channel and send those modes to remote |
273 |
* servers. Using this routine eliminates the necessity to modify the |
274 |
* internal Channel structure and send the command out separately, and also |
275 |
* allows the modes for a channel to be collected up over several calls and |
276 |
* sent out in a single command, decreasing network traffic (and scroll). |
277 |
* This function should be called as either: |
278 |
* set_cmode(sender, channel, modes, param1, param2...) |
279 |
* to send one or more channel modes, or |
280 |
* set_cmode(NULL, channel) |
281 |
* to flush buffered modes for a channel (if `channel' is NULL, flushes |
282 |
* buffered modes for all channels). |
283 |
* |
284 |
* NOTE: When setting modes with parameters, all parameters MUST be |
285 |
* strings. Numeric parameters must be converted to strings (with |
286 |
* snprintf() or the like) before being passed. |
287 |
*/ |
288 |
|
289 |
#define MAXMODES 6 /* Maximum number of mode parameters */ |
290 |
|
291 |
#define MAXPARAMSLEN \ |
292 |
(510- NICKMAX - 6 - CHANMAX -(3+31+2*MAXMODES)-MAXMODES) |
293 |
/* |:sender| | MODE | |#channel| | -...+...| { param}* |
294 |
* Note that "-...+..." can contain at most 31 (binary) plus 2*MAXMODES |
295 |
* (MAXMODES modes with parameters plus +/-) characters. */ |
296 |
|
297 |
static struct modedata { |
298 |
time_t used; |
299 |
Channel *channel; |
300 |
char sender[NICKMAX]; |
301 |
int32 binmodes_on; |
302 |
int32 binmodes_off; |
303 |
char opmodes[MAXMODES*2+1]; |
304 |
char params[MAXMODES][MAXPARAMSLEN+1]; |
305 |
int nopmodes, nparams, paramslen; |
306 |
Timeout *timeout; /* For timely flushing */ |
307 |
} modedata[MERGE_CHANMODES_MAX]; |
308 |
|
309 |
static void possibly_remove_mode(struct modedata *md, char mode, |
310 |
const char *user); |
311 |
static void add_mode_with_params(struct modedata *md, char mode, int is_add, |
312 |
int params, const char *parambuf, int len); |
313 |
static void flush_cmode(struct modedata *md); |
314 |
static void flush_cmode_callback(Timeout *t); |
315 |
|
316 |
/*************************************************************************/ |
317 |
|
318 |
void set_cmode(const char *sender, Channel *channel, ...) |
319 |
{ |
320 |
va_list args; |
321 |
const char *modes, *modes_orig; |
322 |
struct modedata *md; |
323 |
int which = -1, add; |
324 |
int i; |
325 |
char c; |
326 |
|
327 |
|
328 |
/* If `sender' is NULL, flush out pending modes for the channel (for |
329 |
* all channels if `channel' is also NULL) and return. */ |
330 |
if (!sender) { |
331 |
for (i = 0; i < MERGE_CHANMODES_MAX; i++) { |
332 |
if (modedata[i].used && (!channel || modedata[i].channel==channel)) |
333 |
flush_cmode(&modedata[i]); |
334 |
} |
335 |
return; |
336 |
} |
337 |
|
338 |
/* Get the mode string from the argument list; save the original value |
339 |
* for error messages. */ |
340 |
va_start(args, channel); |
341 |
modes = modes_orig = va_arg(args, const char *); |
342 |
|
343 |
/* See if we already have pending modes for the channel; if so, reuse |
344 |
* that entry (if the entry is for a different sender, flush out the |
345 |
* pending modes first). */ |
346 |
for (i = 0; i < MERGE_CHANMODES_MAX; i++) { |
347 |
if (modedata[i].used != 0 && modedata[i].channel == channel) { |
348 |
if (irc_stricmp(modedata[i].sender, sender) != 0) |
349 |
flush_cmode(&modedata[i]); |
350 |
which = i; |
351 |
break; |
352 |
} |
353 |
} |
354 |
/* If there are no pending modes for the channel, look for an empty |
355 |
* slot in the array. */ |
356 |
if (which < 0) { |
357 |
for (i = 0; i < MERGE_CHANMODES_MAX; i++) { |
358 |
if (modedata[i].used == 0) { |
359 |
which = i; |
360 |
break; |
361 |
} |
362 |
} |
363 |
} |
364 |
/* If no slots are free, we'll have to purge one. Find the oldest, |
365 |
* send its modes out, then clear and reuse it. */ |
366 |
if (which < 0) { |
367 |
int oldest = 0; |
368 |
time_t oldest_time = modedata[0].used; |
369 |
for (i = 1; i < MERGE_CHANMODES_MAX; i++) { |
370 |
if (modedata[i].used < oldest_time) { |
371 |
oldest_time = modedata[i].used; |
372 |
oldest = i; |
373 |
} |
374 |
} |
375 |
flush_cmode(&modedata[oldest]); |
376 |
which = oldest; |
377 |
} |
378 |
|
379 |
/* Save a pointer to the entry, then set up sender and channel. */ |
380 |
md = &modedata[which]; |
381 |
strbcpy(md->sender, sender); |
382 |
md->channel = channel; |
383 |
|
384 |
/* Loop through and process all modes in the mode string. */ |
385 |
add = -2; /* -2 means we haven't warned about a missing leading +/- yet */ |
386 |
while ((c = *modes++) != 0) { |
387 |
int32 flag; |
388 |
int params, is_chanuser; |
389 |
|
390 |
log_debug(2, "set_cmode(%s,%s): char=%c(%02X)", |
391 |
sender, channel->name, c<0x20||c>0x7E ? '.' : c, c); |
392 |
|
393 |
/* + and - are handled specially. */ |
394 |
if (c == '+') { |
395 |
add = 1; |
396 |
continue; |
397 |
} else if (c == '-') { |
398 |
add = 0; |
399 |
continue; |
400 |
} |
401 |
/* If we see any other character without first seeing a + or -, |
402 |
* note a bug in the logfile and move along. */ |
403 |
if (add < 0) { |
404 |
if (add == -2) { |
405 |
log("set_cmode(): BUG: mode string `%s' needs leading +/-", |
406 |
modes_orig); |
407 |
add = -1; |
408 |
} |
409 |
continue; |
410 |
} |
411 |
|
412 |
/* Find the flag value and parameter count for the character. */ |
413 |
is_chanuser = 0; |
414 |
flag = mode_char_to_flag(c, MODE_CHANNEL); |
415 |
params = mode_char_to_params(c, MODE_CHANNEL); |
416 |
if (!flag) { |
417 |
is_chanuser = 1; |
418 |
flag = mode_char_to_flag(c, MODE_CHANUSER); |
419 |
params = mode_char_to_params(c, MODE_CHANUSER); |
420 |
if (!flag) { |
421 |
log("set_cmode: bad mode '%c'", c); |
422 |
continue; |
423 |
} |
424 |
} |
425 |
params = (params >> (add*8)) & 0xFF; |
426 |
|
427 |
if (params) { /* Mode with parameters */ |
428 |
char parambuf[BUFSIZE]; /* for putting the parameters in */ |
429 |
int len = 0; |
430 |
|
431 |
if (params > MAXMODES) { |
432 |
/* Sanity check */ |
433 |
fatal("set_cmode(): too many parameters (%d) for mode `%c'\n", |
434 |
params, c); |
435 |
} |
436 |
/* Merge all the parameters into a single string (with no |
437 |
* leading whitespace) */ |
438 |
for (i = 0; i < params; i++) { |
439 |
const char *s = va_arg(args, const char *); |
440 |
log_debug(2, "set_cmode(%s,%s): param=%s", |
441 |
sender, channel->name, s); |
442 |
len += snprintf(parambuf+len, sizeof(parambuf)-len, |
443 |
"%s%s", len ? " " : "", s); |
444 |
} |
445 |
if (flag != MODE_INVALID) { |
446 |
/* If it's a binary mode, see if we've set this mode before. |
447 |
* If so (and if the nick is the same for channel user |
448 |
* modes), remove it; the new one will be appended |
449 |
* afterwards. Note that this assumes that setting each |
450 |
* mode is independent, i.e. that -a+ba 2 1 has the same |
451 |
* effect as +ba 2 1 by itself when +a is set. This doesn't |
452 |
* work for +k/-k, so we let multiple such modes remain. */ |
453 |
if (c != 'k') { |
454 |
possibly_remove_mode(md, c, is_chanuser ? parambuf : NULL); |
455 |
} |
456 |
} |
457 |
add_mode_with_params(md, c, add, params, parambuf, len); |
458 |
} else { /* Binary mode */ |
459 |
/* Note that `flag' should already be set to this value, since |
460 |
* all channel user modes take parameters and thus will never |
461 |
* get here, but just in case... */ |
462 |
flag = mode_char_to_flag(c, MODE_CHANNEL); |
463 |
if (add) { |
464 |
md->binmodes_on |= flag; |
465 |
md->binmodes_off &= ~flag; |
466 |
} else { |
467 |
md->binmodes_off |= flag; |
468 |
md->binmodes_on &= ~flag; |
469 |
} |
470 |
} |
471 |
} |
472 |
va_end(args); |
473 |
md->used = time(NULL); |
474 |
|
475 |
if (MergeChannelModes) { |
476 |
if (!md->timeout) { |
477 |
md->timeout = add_timeout_ms(MergeChannelModes, |
478 |
flush_cmode_callback, 0); |
479 |
md->timeout->data = md; |
480 |
} |
481 |
} |
482 |
} |
483 |
|
484 |
/*************************************************************************/ |
485 |
|
486 |
/* Remove the most recent occurrence of mode `mode' from the mode list if |
487 |
* there is one, provided either `user' is NULL or the parameter associated |
488 |
* with the previous mode is equal (according to irc_stricmp()) to the |
489 |
* string pointed to by `user'. |
490 |
*/ |
491 |
|
492 |
static void possibly_remove_mode(struct modedata *md, char mode, |
493 |
const char *user) |
494 |
{ |
495 |
int i; |
496 |
char *s; |
497 |
|
498 |
log_debug(2, "possibly_remove_mode %c from %.*s%s%s", |
499 |
mode, md->nopmodes*2, md->opmodes, |
500 |
user ? " for user " : "", user ? user : ""); |
501 |
for (i = md->nopmodes-1; i >= 0; i--) { |
502 |
if (md->opmodes[i*2+1] == mode) { |
503 |
/* We've already set this mode once */ |
504 |
if (user) { |
505 |
/* Only remove the old mode if the nick matches */ |
506 |
if (irc_stricmp(md->params[i], user) != 0) |
507 |
continue; |
508 |
} |
509 |
/* Remove the mode */ |
510 |
log_debug(2, " removing mode %d/%d", i, md->nopmodes); |
511 |
md->nopmodes--; |
512 |
s = md->opmodes + (i*2); |
513 |
memmove(s, s+2, strlen(s+2)+1); |
514 |
/* Count parameters for this mode and decrement total by count */ |
515 |
md->nparams--; |
516 |
s = md->params[i]-1; |
517 |
while ((s = strchr(s+1, ' ')) != NULL) |
518 |
md->nparams--; |
519 |
/* Move parameter pointers */ |
520 |
if (i < md->nopmodes) { |
521 |
memmove(md->params+i, md->params+i+1, |
522 |
sizeof(md->params[0])*(md->nopmodes-i)); |
523 |
} |
524 |
/* Clear tail slot */ |
525 |
memset(md->params+md->nopmodes, 0, sizeof(md->params[0])); |
526 |
} |
527 |
} |
528 |
} |
529 |
|
530 |
/*************************************************************************/ |
531 |
|
532 |
/* Add a single mode with parameters to the given mode data structure. |
533 |
* `params' is the number of parameters, `parambuf' is the space-separated |
534 |
* parameter list, and `len' is strlen(parambuf). |
535 |
*/ |
536 |
|
537 |
static void add_mode_with_params(struct modedata *md, char mode, int is_add, |
538 |
int params, const char *parambuf, int len) |
539 |
{ |
540 |
char *s; |
541 |
|
542 |
if (len < 0) { |
543 |
log("add_mode_with_params(): BUG: parameter length < 0 (%d)", len); |
544 |
len = 0; |
545 |
} |
546 |
log_debug(2, "add_mode_with_params: current=%.*s mode=%c add=%d" |
547 |
" params=%d[%.*s]", md->nopmodes*2, md->opmodes, mode, is_add, |
548 |
params, len, parambuf); |
549 |
|
550 |
/* Check for overflow of parameter count or length */ |
551 |
if (md->nparams+params > MAXMODES |
552 |
|| md->paramslen+1+len > MAXPARAMSLEN |
553 |
) { |
554 |
/* Doesn't fit, so flush modes out first */ |
555 |
struct modedata mdtmp = *md; |
556 |
log_debug(2, "add_mode_with_params: ...flushing first"); |
557 |
flush_cmode(md); |
558 |
memcpy(md->sender, mdtmp.sender, sizeof(md->sender)); |
559 |
md->channel = mdtmp.channel; |
560 |
md->used = time(NULL); |
561 |
} |
562 |
s = md->opmodes + 2*md->nopmodes; |
563 |
*s++ = is_add ? '+' : '-'; |
564 |
*s++ = mode; |
565 |
if (len > sizeof(md->params[0])-1) { |
566 |
log("set_cmode(): Parameter string for mode %c%c is too long," |
567 |
" truncating to %d characters", |
568 |
is_add ? '+' : '-', mode, sizeof(md->params[0])-1); |
569 |
len = sizeof(md->params[0])-1; |
570 |
} |
571 |
if (len > 0) |
572 |
memcpy(md->params[md->nopmodes], parambuf, len); |
573 |
md->params[md->nopmodes][len] = 0; |
574 |
md->nopmodes++; |
575 |
md->nparams += params; |
576 |
if (md->paramslen) |
577 |
md->paramslen++; |
578 |
md->paramslen += len; |
579 |
/* If the parameters for this mode alone exceed MAXPARAMSLEN, |
580 |
* we'll now have a string longer than MAXPARAMSLEN in |
581 |
* md->params; not much we can do about it, though, and it'll |
582 |
* get flushed next time around anyway. */ |
583 |
} |
584 |
|
585 |
/*************************************************************************/ |
586 |
|
587 |
/* Flush out pending mode changes for the given mode data structure. If |
588 |
* `clear' is nonzero, clear the entry, else leave it alone. |
589 |
*/ |
590 |
|
591 |
static void flush_cmode(struct modedata *md) |
592 |
{ |
593 |
char buf[BUFSIZE], *s; |
594 |
char *argv[MAXMODES+2]; |
595 |
int len = 0, i; |
596 |
char lastc = 0; |
597 |
|
598 |
/* Clear timeout for this entry if one is set */ |
599 |
if (md->timeout) { |
600 |
del_timeout(md->timeout); |
601 |
md->timeout = NULL; |
602 |
} |
603 |
|
604 |
if (!md->channel) { |
605 |
/* This entry is unused, just return */ |
606 |
goto done; |
607 |
} |
608 |
if (!md->binmodes_on && !md->binmodes_off && !*md->opmodes) { |
609 |
/* No actual modes here */ |
610 |
goto done; |
611 |
} |
612 |
|
613 |
if (debug >= 2) { |
614 |
char onbuf[512]; |
615 |
strbcpy(onbuf, mode_flags_to_string(md->binmodes_on,MODE_CHANNEL)); |
616 |
log_debug(2, "flush_cmode(%s): bin_on=%s bin_off=%s opmodes=%d(%.*s)", |
617 |
md->channel->name, onbuf, |
618 |
mode_flags_to_string(md->binmodes_off, MODE_CHANNEL), |
619 |
md->nopmodes, md->nopmodes*2, md->opmodes); |
620 |
} |
621 |
|
622 |
/* Note that - must come before + because some servers (Unreal, others?) |
623 |
* ignore +s if followed by -p. */ |
624 |
if (md->binmodes_off) { |
625 |
len += snprintf(buf+len, sizeof(buf)-len, "-%s", |
626 |
mode_flags_to_string(md->binmodes_off, MODE_CHANNEL)); |
627 |
lastc = '-'; |
628 |
} |
629 |
if (md->binmodes_on) { |
630 |
len += snprintf(buf+len, sizeof(buf)-len, "+%s", |
631 |
mode_flags_to_string(md->binmodes_on, MODE_CHANNEL)); |
632 |
lastc = '+'; |
633 |
} |
634 |
s = md->opmodes; |
635 |
while (*s) { |
636 |
if (*s == lastc) { |
637 |
/* +/- matches last mode change */ |
638 |
s++; |
639 |
} else { |
640 |
if (len < sizeof(buf)-1) |
641 |
buf[len++] = *s; |
642 |
else |
643 |
fatal("BUG: buf too small in flush_cmode() (1)"); |
644 |
lastc = *s++; |
645 |
} |
646 |
if (len < sizeof(buf)-1) { |
647 |
buf[len++] = *s; |
648 |
buf[len] = 0; |
649 |
} else { |
650 |
fatal("BUG: buf too small in flush_cmode() (2)"); |
651 |
} |
652 |
s++; |
653 |
} |
654 |
for (i = 0; i < md->nopmodes; i++) { |
655 |
if (*md->params[i]) |
656 |
len += snprintf(buf+len, sizeof(buf)-len, " %s", md->params[i]); |
657 |
} |
658 |
|
659 |
/* Actually send the command */ |
660 |
send_cmode_cmd(md->sender, md->channel->name, "%s", buf); |
661 |
|
662 |
/* Split buffer back up into individual parameters for do_cmode(). |
663 |
* This is inefficient, but taking the faster route of setting modes |
664 |
* when they are sent to set_cmode() runs the risk of temporary desyncs. |
665 |
* (Example: SomeNick enters #channel -> autoop, but delayed -> SomeNick |
666 |
* does /cs op SomeNick -> ChanServ says "SomeNick is already opped" -> |
667 |
* SomeNick goes "Huh?") |
668 |
*/ |
669 |
argv[0] = md->channel->name; |
670 |
s = buf; |
671 |
for (i = 0; i <= md->nparams; i++) { |
672 |
argv[i+1] = s; |
673 |
s = strchr(s, ' '); |
674 |
if (!s) { |
675 |
md->nparams = i; |
676 |
break; |
677 |
} |
678 |
*s++ = 0; |
679 |
} |
680 |
/* Clear md->channel so a recursive set_cmode() doesn't find this entry |
681 |
* and try to use/flush it */ |
682 |
md->channel = NULL; |
683 |
/* Adjust our idea of the channel modes */ |
684 |
do_cmode(md->sender, md->nparams+2, argv); |
685 |
|
686 |
done: |
687 |
/* Clear entry and return */ |
688 |
memset(md, 0, sizeof(*md)); |
689 |
} |
690 |
|
691 |
/*************************************************************************/ |
692 |
|
693 |
/* Timeout called to flush mode changes for a channel after |
694 |
* `MergeChannelModes' seconds of inactivity. |
695 |
*/ |
696 |
|
697 |
static void flush_cmode_callback(Timeout *t) |
698 |
{ |
699 |
flush_cmode((struct modedata *)t->data); |
700 |
} |
701 |
|
702 |
/*************************************************************************/ |
703 |
|
704 |
/* |
705 |
* Local variables: |
706 |
* c-file-style: "stroustrup" |
707 |
* c-file-offsets: ((case-label . *) (statement-case-intro . *)) |
708 |
* indent-tabs-mode: nil |
709 |
* End: |
710 |
* |
711 |
* vim: expandtab shiftwidth=4: |
712 |
*/ |