]>
Commit | Line | Data |
---|---|---|
189935b1 | 1 | /* |
2 | * IRC - Internet Relay Chat, ircd/channel.c | |
3 | * Copyright (C) 1990 Jarkko Oikarinen and | |
4 | * University of Oulu, Co Center | |
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 1, 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 Channel management and maintenance | |
9fe6dff2 | 22 | * @version $Id: channel.c,v 1.155.2.18 2007/08/15 02:49:54 entrope Exp $ |
189935b1 | 23 | */ |
24 | #include "config.h" | |
25 | ||
26 | #include "channel.h" | |
27 | #include "client.h" | |
28 | #include "destruct_event.h" | |
29 | #include "hash.h" | |
30 | #include "ircd.h" | |
31 | #include "ircd_alloc.h" | |
32 | #include "ircd_chattr.h" | |
33 | #include "ircd_defs.h" | |
34 | #include "ircd_features.h" | |
35 | #include "ircd_log.h" | |
36 | #include "ircd_reply.h" | |
37 | #include "ircd_snprintf.h" | |
38 | #include "ircd_string.h" | |
39 | #include "list.h" | |
40 | #include "match.h" | |
41 | #include "msg.h" | |
42 | #include "msgq.h" | |
43 | #include "numeric.h" | |
44 | #include "numnicks.h" | |
45 | #include "querycmds.h" | |
46 | #include "s_bsd.h" | |
47 | #include "s_conf.h" | |
48 | #include "s_debug.h" | |
49 | #include "s_misc.h" | |
50 | #include "s_user.h" | |
51 | #include "send.h" | |
52 | #include "struct.h" | |
53 | #include "sys.h" | |
54 | #include "whowas.h" | |
55 | ||
56 | /* #include <assert.h> -- Now using assert in ircd_log.h */ | |
57 | #include <stdio.h> | |
58 | #include <stdlib.h> | |
59 | #include <string.h> | |
60 | ||
61 | /** Linked list containing the full list of all channels */ | |
62 | struct Channel* GlobalChannelList = 0; | |
63 | ||
64 | /** Number of struct Membership*'s allocated */ | |
65 | static unsigned int membershipAllocCount; | |
66 | /** Freelist for struct Membership*'s */ | |
67 | static struct Membership* membershipFreeList; | |
68 | /** Freelist for struct Ban*'s */ | |
69 | static struct Ban* free_bans; | |
70 | /** Number of ban structures allocated. */ | |
71 | static size_t bans_alloc; | |
72 | /** Number of ban structures in use. */ | |
73 | static size_t bans_inuse; | |
74 | ||
75 | #if !defined(NDEBUG) | |
76 | /** return the length (>=0) of a chain of links. | |
77 | * @param lp pointer to the start of the linked list | |
78 | * @return the number of items in the list | |
79 | */ | |
80 | static int list_length(struct SLink *lp) | |
81 | { | |
82 | int count = 0; | |
83 | ||
84 | for (; lp; lp = lp->next) | |
85 | ++count; | |
86 | return count; | |
87 | } | |
88 | #endif | |
89 | ||
90 | /** Set the mask for a ban, checking for IP masks. | |
91 | * @param[in,out] ban Ban structure to modify. | |
92 | * @param[in] banstr Mask to ban. | |
93 | */ | |
94 | static void | |
95 | set_ban_mask(struct Ban *ban, const char *banstr) | |
96 | { | |
97 | char *sep; | |
98 | assert(banstr != NULL); | |
99 | ircd_strncpy(ban->banstr, banstr, sizeof(ban->banstr) - 1); | |
100 | sep = strrchr(banstr, '@'); | |
101 | if (sep) { | |
102 | ban->nu_len = sep - banstr; | |
103 | if (ipmask_parse(sep + 1, &ban->address, &ban->addrbits)) | |
104 | ban->flags |= BAN_IPMASK; | |
105 | } | |
106 | } | |
107 | ||
108 | /** Allocate a new Ban structure. | |
109 | * @param[in] banstr Ban mask to use. | |
110 | * @return Newly allocated ban. | |
111 | */ | |
112 | struct Ban * | |
113 | make_ban(const char *banstr) | |
114 | { | |
115 | struct Ban *ban; | |
116 | if (free_bans) { | |
117 | ban = free_bans; | |
118 | free_bans = free_bans->next; | |
119 | } | |
120 | else if (!(ban = MyMalloc(sizeof(*ban)))) | |
121 | return NULL; | |
122 | else | |
123 | bans_alloc++; | |
124 | bans_inuse++; | |
125 | memset(ban, 0, sizeof(*ban)); | |
126 | set_ban_mask(ban, banstr); | |
127 | return ban; | |
128 | } | |
129 | ||
130 | /** Deallocate a ban structure. | |
131 | * @param[in] ban Ban to deallocate. | |
132 | */ | |
133 | void | |
134 | free_ban(struct Ban *ban) | |
135 | { | |
136 | ban->next = free_bans; | |
137 | free_bans = ban; | |
138 | bans_inuse--; | |
139 | } | |
140 | ||
141 | /** Report ban usage to \a cptr. | |
142 | * @param[in] cptr Client requesting information. | |
143 | */ | |
144 | void bans_send_meminfo(struct Client *cptr) | |
145 | { | |
146 | struct Ban *ban; | |
147 | size_t num_free; | |
148 | for (num_free = 0, ban = free_bans; ban; ban = ban->next) | |
149 | num_free++; | |
150 | send_reply(cptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Bans: inuse %zu(%zu) free %zu alloc %zu", | |
151 | bans_inuse, bans_inuse * sizeof(*ban), num_free, bans_alloc); | |
152 | } | |
153 | ||
154 | /** return the struct Membership* that represents a client on a channel | |
155 | * This function finds a struct Membership* which holds the state about | |
156 | * a client on a specific channel. The code is smart enough to iterate | |
157 | * over the channels a user is in, or the users in a channel to find the | |
158 | * user depending on which is likely to be more efficient. | |
159 | * | |
160 | * @param chptr pointer to the channel struct | |
161 | * @param cptr pointer to the client struct | |
162 | * | |
163 | * @returns pointer to the struct Membership representing this client on | |
164 | * this channel. Returns NULL if the client is not on the channel. | |
165 | * Returns NULL if the client is actually a server. | |
166 | * @see find_channel_member() | |
167 | */ | |
168 | struct Membership* find_member_link(struct Channel* chptr, const struct Client* cptr) | |
169 | { | |
170 | struct Membership *m; | |
171 | assert(0 != cptr); | |
172 | assert(0 != chptr); | |
173 | ||
174 | /* Servers don't have member links */ | |
175 | if (IsServer(cptr)||IsMe(cptr)) | |
176 | return 0; | |
177 | ||
178 | /* +k users are typically on a LOT of channels. So we iterate over who | |
179 | * is in the channel. X/W are +k and are in about 5800 channels each. | |
180 | * however there are typically no more than 1000 people in a channel | |
181 | * at a time. | |
182 | */ | |
183 | if (IsChannelService(cptr)) { | |
184 | m = chptr->members; | |
185 | while (m) { | |
186 | assert(m->channel == chptr); | |
187 | if (m->user == cptr) | |
188 | return m; | |
189 | m = m->next_member; | |
190 | } | |
191 | } | |
192 | /* Users on the other hand aren't allowed on more than 15 channels. 50% | |
193 | * of users that are on channels are on 2 or less, 95% are on 7 or less, | |
194 | * and 99% are on 10 or less. | |
195 | */ | |
196 | else { | |
197 | m = (cli_user(cptr))->channel; | |
198 | while (m) { | |
199 | assert(m->user == cptr); | |
200 | if (m->channel == chptr) | |
201 | return m; | |
202 | m = m->next_channel; | |
203 | } | |
204 | } | |
205 | return 0; | |
206 | } | |
207 | ||
208 | /** Find the client structure for a nick name (user) | |
209 | * Find the client structure for a nick name (user) | |
210 | * using history mechanism if necessary. If the client is not found, an error | |
211 | * message (NO SUCH NICK) is generated. If the client was found | |
212 | * through the history, chasing will be 1 and otherwise 0. | |
213 | * | |
214 | * This function was used extensively in the P09 days, and since we now have | |
215 | * numeric nicks is no longer quite as important. | |
216 | * | |
217 | * @param sptr Pointer to the client that has requested the search | |
218 | * @param user a string representing the client to be found | |
219 | * @param chasing a variable set to 0 if the user was found directly, | |
220 | * 1 otherwise | |
221 | * @returns a pointer the client, or NULL if the client wasn't found. | |
222 | */ | |
223 | struct Client* find_chasing(struct Client* sptr, const char* user, int* chasing) | |
224 | { | |
225 | struct Client* who = FindClient(user); | |
226 | ||
227 | if (chasing) | |
228 | *chasing = 0; | |
229 | if (who) | |
230 | return who; | |
231 | ||
232 | if (!(who = get_history(user, feature_int(FEAT_KILLCHASETIMELIMIT)))) { | |
233 | send_reply(sptr, ERR_NOSUCHNICK, user); | |
234 | return 0; | |
235 | } | |
236 | if (chasing) | |
237 | *chasing = 1; | |
238 | return who; | |
239 | } | |
240 | ||
241 | /** Decrement the count of users, and free if empty. | |
242 | * Subtract one user from channel i (and free channel * block, if channel | |
243 | * became empty). | |
244 | * | |
245 | * @param chptr The channel to subtract one from. | |
246 | * | |
247 | * @returns true (1) if channel still has members. | |
248 | * false (0) if the channel is now empty. | |
249 | */ | |
250 | int sub1_from_channel(struct Channel* chptr) | |
251 | { | |
252 | if (chptr->users > 1) /* Can be 0, called for an empty channel too */ | |
253 | { | |
254 | assert(0 != chptr->members); | |
255 | --chptr->users; | |
256 | return 1; | |
257 | } | |
258 | ||
259 | chptr->users = 0; | |
260 | ||
052b069e | 261 | /* |
262 | * Also channels without Apass set need to be kept alive, | |
263 | * otherwise Bad Guys(tm) would be able to takeover | |
264 | * existing channels too easily, and then set an Apass! | |
265 | * However, if a channel without Apass becomes empty | |
266 | * then we try to be kind to them and remove possible | |
267 | * limiting modes. | |
268 | */ | |
269 | chptr->mode.mode &= ~MODE_INVITEONLY; | |
270 | chptr->mode.limit = 0; | |
271 | /* | |
272 | * We do NOT reset a possible key or bans because when | |
273 | * the 'channel owners' can't get in because of a key | |
274 | * or ban then apparently there was a fight/takeover | |
275 | * on the channel and we want them to contact IRC opers | |
276 | * who then will educate them on the use of Apass/Upass. | |
189935b1 | 277 | */ |
052b069e | 278 | if (!chptr->mode.apass[0]) /* If no Apass, reset all modes. */ |
279 | { | |
280 | struct Ban *link, *next; | |
281 | chptr->mode.mode = 0; | |
282 | *chptr->mode.key = '\0'; | |
283 | while (chptr->invites) | |
284 | del_invite(chptr->invites->value.cptr, chptr); | |
285 | for (link = chptr->banlist; link; link = next) { | |
286 | next = link->next; | |
287 | free_ban(link); | |
288 | } | |
289 | chptr->banlist = NULL; | |
290 | ||
291 | /* Immediately destruct empty -A channels if not using apass. */ | |
292 | if (!feature_bool(FEAT_OPLEVELS)) | |
293 | { | |
294 | destruct_channel(chptr); | |
295 | return 0; | |
296 | } | |
297 | } | |
298 | if (TStime() - chptr->creationtime < 172800) /* Channel younger than 48 hours? */ | |
189935b1 | 299 | schedule_destruct_event_1m(chptr); /* Get rid of it in approximately 4-5 minutes */ |
300 | else | |
301 | schedule_destruct_event_48h(chptr); /* Get rid of it in approximately 48 hours */ | |
302 | ||
303 | return 0; | |
304 | } | |
305 | ||
306 | /** Destroy an empty channel | |
307 | * This function destroys an empty channel, removing it from hashtables, | |
308 | * and removing any resources it may have consumed. | |
309 | * | |
310 | * @param chptr The channel to destroy | |
311 | * | |
312 | * @returns 0 (success) | |
313 | * | |
314 | * FIXME: Change to return void, this function never fails. | |
315 | */ | |
316 | int destruct_channel(struct Channel* chptr) | |
317 | { | |
318 | struct Ban *ban, *next; | |
319 | ||
320 | assert(0 == chptr->members); | |
321 | ||
322 | /* | |
323 | * Now, find all invite links from channel structure | |
324 | */ | |
325 | while (chptr->invites) | |
326 | del_invite(chptr->invites->value.cptr, chptr); | |
327 | ||
328 | for (ban = chptr->banlist; ban; ban = next) | |
329 | { | |
330 | next = ban->next; | |
331 | free_ban(ban); | |
332 | } | |
333 | if (chptr->prev) | |
334 | chptr->prev->next = chptr->next; | |
335 | else | |
336 | GlobalChannelList = chptr->next; | |
337 | if (chptr->next) | |
338 | chptr->next->prev = chptr->prev; | |
339 | hRemChannel(chptr); | |
340 | --UserStats.channels; | |
341 | /* | |
342 | * make sure that channel actually got removed from hash table | |
343 | */ | |
344 | assert(chptr->hnext == chptr); | |
345 | MyFree(chptr); | |
346 | return 0; | |
347 | } | |
348 | ||
349 | /** returns Membership * if a person is joined and not a zombie | |
350 | * @param cptr Client | |
351 | * @param chptr Channel | |
352 | * @returns pointer to the client's struct Membership * on the channel if that | |
353 | * user is a full member of the channel, or NULL otherwise. | |
354 | * | |
355 | * @see find_member_link() | |
356 | */ | |
357 | struct Membership* find_channel_member(struct Client* cptr, struct Channel* chptr) | |
358 | { | |
359 | struct Membership* member; | |
360 | assert(0 != chptr); | |
361 | ||
362 | member = find_member_link(chptr, cptr); | |
363 | return (member && !IsZombie(member)) ? member : 0; | |
364 | } | |
365 | ||
366 | /** Searches for a ban from a ban list that matches a user. | |
367 | * @param[in] cptr The client to test. | |
368 | * @param[in] banlist The list of bans to test. | |
369 | * @return Pointer to a matching ban, or NULL if none exit. | |
370 | */ | |
371 | struct Ban *find_ban(struct Client *cptr, struct Ban *banlist) | |
372 | { | |
373 | char nu[NICKLEN + USERLEN + 2]; | |
374 | char tmphost[HOSTLEN + 1]; | |
375 | char iphost[SOCKIPLEN + 1]; | |
376 | char *hostmask; | |
377 | char *sr; | |
378 | struct Ban *found; | |
379 | ||
380 | /* Build nick!user and alternate host names. */ | |
381 | ircd_snprintf(0, nu, sizeof(nu), "%s!%s", | |
382 | cli_name(cptr), cli_user(cptr)->username); | |
383 | ircd_ntoa_r(iphost, &cli_ip(cptr)); | |
384 | if (!IsAccount(cptr)) | |
385 | sr = NULL; | |
d8e74551 | 386 | else if (HasHiddenHost(cptr) || HasSetHost(cptr)) |
189935b1 | 387 | sr = cli_user(cptr)->realhost; |
388 | else | |
389 | { | |
390 | ircd_snprintf(0, tmphost, HOSTLEN, "%s.%s", | |
391 | cli_user(cptr)->account, feature_str(FEAT_HIDDEN_HOST)); | |
392 | sr = tmphost; | |
393 | } | |
394 | ||
395 | /* Walk through ban list. */ | |
396 | for (found = NULL; banlist; banlist = banlist->next) { | |
397 | int res; | |
398 | /* If we have found a positive ban already, only consider exceptions. */ | |
399 | if (found && !(banlist->flags & BAN_EXCEPTION)) | |
400 | continue; | |
401 | /* Compare nick!user portion of ban. */ | |
402 | banlist->banstr[banlist->nu_len] = '\0'; | |
403 | res = match(banlist->banstr, nu); | |
404 | banlist->banstr[banlist->nu_len] = '@'; | |
405 | if (res) | |
406 | continue; | |
407 | /* Compare host portion of ban. */ | |
408 | hostmask = banlist->banstr + banlist->nu_len + 1; | |
409 | if (!((banlist->flags & BAN_IPMASK) | |
410 | && ipmask_check(&cli_ip(cptr), &banlist->address, banlist->addrbits)) | |
411 | && match(hostmask, cli_user(cptr)->host) | |
412 | && !(sr && !match(hostmask, sr))) | |
413 | continue; | |
414 | /* If an exception matches, no ban can match. */ | |
415 | if (banlist->flags & BAN_EXCEPTION) | |
416 | return NULL; | |
417 | /* Otherwise, remember this ban but keep searching for an exception. */ | |
418 | found = banlist; | |
419 | } | |
420 | return found; | |
421 | } | |
422 | ||
423 | /** | |
424 | * This function returns true if the user is banned on the said channel. | |
425 | * This function will check the ban cache if applicable, otherwise will | |
426 | * do the comparisons and cache the result. | |
427 | * | |
428 | * @param[in] member The Membership to test for banned-ness. | |
429 | * @return Non-zero if the member is banned, zero if not. | |
430 | */ | |
431 | static int is_banned(struct Membership* member) | |
432 | { | |
433 | if (IsBanValid(member)) | |
434 | return IsBanned(member); | |
435 | ||
436 | SetBanValid(member); | |
437 | if (find_ban(member->user, member->channel->banlist)) { | |
438 | SetBanned(member); | |
439 | return 1; | |
440 | } else { | |
441 | ClearBanned(member); | |
442 | return 0; | |
443 | } | |
444 | } | |
445 | ||
446 | /** add a user to a channel. | |
447 | * adds a user to a channel by adding another link to the channels member | |
448 | * chain. | |
449 | * | |
450 | * @param chptr The channel to add to. | |
451 | * @param who The user to add. | |
452 | * @param flags The flags the user gets initially. | |
453 | * @param oplevel The oplevel the user starts with. | |
454 | */ | |
455 | void add_user_to_channel(struct Channel* chptr, struct Client* who, | |
456 | unsigned int flags, int oplevel) | |
457 | { | |
458 | assert(0 != chptr); | |
459 | assert(0 != who); | |
460 | ||
461 | if (cli_user(who)) { | |
462 | ||
463 | struct Membership* member = membershipFreeList; | |
464 | if (member) | |
465 | membershipFreeList = member->next_member; | |
466 | else { | |
467 | member = (struct Membership*) MyMalloc(sizeof(struct Membership)); | |
468 | ++membershipAllocCount; | |
469 | } | |
470 | ||
471 | assert(0 != member); | |
472 | member->user = who; | |
473 | member->channel = chptr; | |
474 | member->status = flags; | |
475 | SetOpLevel(member, oplevel); | |
476 | ||
477 | member->next_member = chptr->members; | |
478 | if (member->next_member) | |
479 | member->next_member->prev_member = member; | |
480 | member->prev_member = 0; | |
481 | chptr->members = member; | |
482 | ||
483 | member->next_channel = (cli_user(who))->channel; | |
484 | if (member->next_channel) | |
485 | member->next_channel->prev_channel = member; | |
486 | member->prev_channel = 0; | |
487 | (cli_user(who))->channel = member; | |
488 | ||
489 | if (chptr->destruct_event) | |
490 | remove_destruct_event(chptr); | |
491 | ++chptr->users; | |
492 | ++((cli_user(who))->joined); | |
493 | } | |
494 | } | |
495 | ||
496 | /** Remove a person from a channel, given their Membership* | |
497 | * | |
498 | * @param member A member of a channel. | |
499 | * | |
500 | * @returns true if there are more people in the channel. | |
501 | */ | |
502 | static int remove_member_from_channel(struct Membership* member) | |
503 | { | |
504 | struct Channel* chptr; | |
505 | assert(0 != member); | |
506 | chptr = member->channel; | |
507 | /* | |
508 | * unlink channel member list | |
509 | */ | |
510 | if (member->next_member) | |
511 | member->next_member->prev_member = member->prev_member; | |
512 | if (member->prev_member) | |
513 | member->prev_member->next_member = member->next_member; | |
514 | else | |
515 | member->channel->members = member->next_member; | |
516 | ||
517 | /* | |
518 | * If this is the last delayed-join user, may have to clear WASDELJOINS. | |
519 | */ | |
520 | if (IsDelayedJoin(member)) | |
521 | CheckDelayedJoins(chptr); | |
522 | ||
523 | /* | |
524 | * unlink client channel list | |
525 | */ | |
526 | if (member->next_channel) | |
527 | member->next_channel->prev_channel = member->prev_channel; | |
528 | if (member->prev_channel) | |
529 | member->prev_channel->next_channel = member->next_channel; | |
530 | else | |
531 | (cli_user(member->user))->channel = member->next_channel; | |
532 | ||
533 | --(cli_user(member->user))->joined; | |
534 | ||
535 | member->next_member = membershipFreeList; | |
536 | membershipFreeList = member; | |
537 | ||
538 | return sub1_from_channel(chptr); | |
539 | } | |
540 | ||
541 | /** Check if all the remaining members on the channel are zombies | |
542 | * | |
543 | * @returns False if the channel has any non zombie members, True otherwise. | |
544 | * @see \ref zombie | |
545 | */ | |
546 | static int channel_all_zombies(struct Channel* chptr) | |
547 | { | |
548 | struct Membership* member; | |
549 | ||
550 | for (member = chptr->members; member; member = member->next_member) { | |
551 | if (!IsZombie(member)) | |
552 | return 0; | |
553 | } | |
554 | return 1; | |
555 | } | |
556 | ||
557 | ||
558 | /** Remove a user from a channel | |
559 | * This is the generic entry point for removing a user from a channel, this | |
560 | * function will remove the client from the channel, and destroy the channel | |
561 | * if there are no more normal users left. | |
562 | * | |
563 | * @param cptr The client | |
564 | * @param chptr The channel | |
565 | */ | |
566 | void remove_user_from_channel(struct Client* cptr, struct Channel* chptr) | |
567 | { | |
568 | ||
569 | struct Membership* member; | |
570 | assert(0 != chptr); | |
571 | ||
572 | if ((member = find_member_link(chptr, cptr))) { | |
573 | if (remove_member_from_channel(member)) { | |
574 | if (channel_all_zombies(chptr)) { | |
575 | /* | |
576 | * XXX - this looks dangerous but isn't if we got the referential | |
577 | * integrity right for channels | |
578 | */ | |
579 | while (remove_member_from_channel(chptr->members)) | |
580 | ; | |
581 | } | |
582 | } | |
583 | } | |
584 | } | |
585 | ||
586 | /** Remove a user from all channels they are on. | |
587 | * | |
588 | * This function removes a user from all channels they are on. | |
589 | * | |
590 | * @param cptr The client to remove. | |
591 | */ | |
592 | void remove_user_from_all_channels(struct Client* cptr) | |
593 | { | |
594 | struct Membership* chan; | |
595 | assert(0 != cptr); | |
596 | assert(0 != cli_user(cptr)); | |
597 | ||
598 | while ((chan = (cli_user(cptr))->channel)) | |
599 | remove_user_from_channel(cptr, chan->channel); | |
600 | } | |
601 | ||
602 | /** Check if this user is a legitimate chanop | |
603 | * | |
604 | * @param cptr Client to check | |
605 | * @param chptr Channel to check | |
606 | * | |
607 | * @returns True if the user is a chanop (And not a zombie), False otherwise. | |
608 | * @see \ref zombie | |
609 | */ | |
610 | int is_chan_op(struct Client *cptr, struct Channel *chptr) | |
611 | { | |
612 | struct Membership* member; | |
613 | assert(chptr); | |
614 | if ((member = find_member_link(chptr, cptr))) | |
615 | return (!IsZombie(member) && IsChanOp(member)); | |
616 | ||
617 | return 0; | |
618 | } | |
619 | ||
620 | /** Check if a user is a Zombie on a specific channel. | |
621 | * | |
622 | * @param cptr The client to check. | |
623 | * @param chptr The channel to check. | |
624 | * | |
625 | * @returns True if the client (cptr) is a zombie on the channel (chptr), | |
626 | * False otherwise. | |
627 | * | |
628 | * @see \ref zombie | |
629 | */ | |
630 | int is_zombie(struct Client *cptr, struct Channel *chptr) | |
631 | { | |
632 | struct Membership* member; | |
633 | ||
634 | assert(0 != chptr); | |
635 | ||
636 | if ((member = find_member_link(chptr, cptr))) | |
637 | return IsZombie(member); | |
638 | return 0; | |
639 | } | |
640 | ||
641 | /** Returns if a user has voice on a channel. | |
642 | * | |
643 | * @param cptr The client | |
644 | * @param chptr The channel | |
645 | * | |
646 | * @returns True if the client (cptr) is voiced on (chptr) and is not a zombie. | |
647 | * @see \ref zombie | |
648 | */ | |
649 | int has_voice(struct Client* cptr, struct Channel* chptr) | |
650 | { | |
651 | struct Membership* member; | |
652 | ||
653 | assert(0 != chptr); | |
654 | if ((member = find_member_link(chptr, cptr))) | |
655 | return (!IsZombie(member) && HasVoice(member)); | |
656 | ||
657 | return 0; | |
658 | } | |
659 | ||
660 | /** Can this member send to a channel | |
661 | * | |
662 | * A user can speak on a channel iff: | |
663 | * <ol> | |
664 | * <li> They didn't use the Apass to gain ops. | |
665 | * <li> They are op'd or voice'd. | |
666 | * <li> You aren't banned. | |
667 | * <li> The channel isn't +m | |
668 | * <li> The channel isn't +n or you are on the channel. | |
669 | * </ol> | |
670 | * | |
671 | * This function will optionally reveal a user on a delayed join channel if | |
672 | * they are allowed to send to the channel. | |
673 | * | |
674 | * @param member The membership of the user | |
675 | * @param reveal If true, the user will be "revealed" on a delayed | |
052b069e | 676 | * joined channel. |
189935b1 | 677 | * |
678 | * @returns True if the client can speak on the channel. | |
679 | */ | |
680 | int member_can_send_to_channel(struct Membership* member, int reveal) | |
681 | { | |
682 | assert(0 != member); | |
683 | ||
052b069e | 684 | /* Do not check for users on other servers: This should be a |
685 | * temporary desynch, or maybe they are on an older server, but | |
686 | * we do not want to send ERR_CANNOTSENDTOCHAN more than once. | |
687 | */ | |
688 | if (!MyUser(member->user)) | |
689 | { | |
690 | if (IsDelayedJoin(member) && reveal) | |
691 | RevealDelayedJoin(member); | |
692 | return 1; | |
693 | } | |
694 | ||
695 | /* Discourage using the Apass to get op. They should use the Upass. */ | |
189935b1 | 696 | if (IsChannelManager(member) && member->channel->mode.apass[0]) |
697 | return 0; | |
698 | ||
052b069e | 699 | /* If you have voice or ops, you can speak. */ |
189935b1 | 700 | if (IsVoicedOrOpped(member)) |
701 | return 1; | |
702 | ||
703 | /* | |
704 | * If it's moderated, and you aren't a privileged user, you can't | |
705 | * speak. | |
706 | */ | |
707 | if (member->channel->mode.mode & MODE_MODERATED) | |
708 | return 0; | |
052b069e | 709 | |
189935b1 | 710 | /* If only logged in users may join and you're not one, you can't speak. */ |
1cade670 | 711 | if (member->channel->mode.mode & (MODE_MODERATENOREG|MODE_REGONLY) && !IsAccount(member->user)) |
189935b1 | 712 | return 0; |
052b069e | 713 | |
714 | /* If you're banned then you can't speak either. */ | |
715 | if (is_banned(member)) | |
189935b1 | 716 | return 0; |
717 | ||
718 | if (IsDelayedJoin(member) && reveal) | |
719 | RevealDelayedJoin(member); | |
720 | ||
721 | return 1; | |
722 | } | |
723 | ||
724 | /** Check if a client can send to a channel. | |
725 | * | |
726 | * Has the added check over member_can_send_to_channel() of servers can | |
727 | * always speak. | |
728 | * | |
729 | * @param cptr The client to check | |
730 | * @param chptr The channel to check | |
731 | * @param reveal If the user should be revealed (see | |
732 | * member_can_send_to_channel()) | |
733 | * | |
734 | * @returns true if the client is allowed to speak on the channel, false | |
735 | * otherwise | |
736 | * | |
737 | * @see member_can_send_to_channel() | |
738 | */ | |
739 | int client_can_send_to_channel(struct Client *cptr, struct Channel *chptr, int reveal) | |
740 | { | |
741 | struct Membership *member; | |
742 | assert(0 != cptr); | |
743 | /* | |
744 | * Servers can always speak on channels. | |
745 | */ | |
d8e74551 | 746 | if (IsServer(cptr) || IsXtraOp(cptr)) |
189935b1 | 747 | return 1; |
748 | ||
749 | member = find_channel_member(cptr, chptr); | |
750 | ||
751 | /* | |
752 | * You can't speak if you're off channel, and it is +n (no external messages) | |
753 | * or +m (moderated). | |
754 | */ | |
755 | if (!member) { | |
756 | if ((chptr->mode.mode & (MODE_NOPRIVMSGS|MODE_MODERATED)) || | |
1cade670 | 757 | ((chptr->mode.mode & (MODE_REGONLY|MODE_MODERATENOREG)) && !IsAccount(cptr))) |
189935b1 | 758 | return 0; |
759 | else | |
760 | return !find_ban(cptr, chptr->banlist); | |
761 | } | |
762 | return member_can_send_to_channel(member, reveal); | |
763 | } | |
764 | ||
765 | /** Returns the name of a channel that prevents the user from changing nick. | |
766 | * if a member and not (opped or voiced) and (banned or moderated), return | |
767 | * the name of the first channel banned on. | |
768 | * | |
769 | * @param cptr The client | |
770 | * | |
771 | * @returns the name of the first channel banned on, or NULL if the user | |
772 | * can change nicks. | |
773 | */ | |
774 | const char* find_no_nickchange_channel(struct Client* cptr) | |
775 | { | |
776 | if (MyUser(cptr)) { | |
777 | struct Membership* member; | |
778 | for (member = (cli_user(cptr))->channel; member; | |
779 | member = member->next_channel) { | |
052b069e | 780 | if (IsVoicedOrOpped(member)) |
781 | continue; | |
782 | if ((member->channel->mode.mode & MODE_MODERATED) | |
1cade670 | 783 | || (member->channel->mode.mode & (MODE_MODERATENOREG|MODE_REGONLY) && !IsAccount(cptr)) |
052b069e | 784 | || is_banned(member)) |
189935b1 | 785 | return member->channel->chname; |
786 | } | |
787 | } | |
788 | return 0; | |
789 | } | |
790 | ||
791 | ||
792 | /** Fill mbuf/pbuf with modes from chptr | |
793 | * write the "simple" list of channel modes for channel chptr onto buffer mbuf | |
794 | * with the parameters in pbuf as visible by cptr. | |
795 | * | |
796 | * This function will hide keys from non-op'd, non-server clients. | |
797 | * | |
798 | * @param cptr The client to generate the mode for. | |
799 | * @param mbuf The buffer to write the modes into. | |
800 | * @param pbuf The buffer to write the mode parameters into. | |
801 | * @param buflen The length of the buffers. | |
802 | * @param chptr The channel to get the modes from. | |
803 | * @param member The membership of this client on this channel (or NULL | |
804 | * if this client isn't on this channel) | |
805 | * | |
806 | */ | |
807 | void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen, | |
808 | struct Channel *chptr, struct Membership *member) | |
809 | { | |
810 | int previous_parameter = 0; | |
811 | ||
812 | assert(0 != mbuf); | |
813 | assert(0 != pbuf); | |
814 | assert(0 != chptr); | |
815 | ||
816 | *mbuf++ = '+'; | |
817 | if (chptr->mode.mode & MODE_SECRET) | |
818 | *mbuf++ = 's'; | |
819 | else if (chptr->mode.mode & MODE_PRIVATE) | |
820 | *mbuf++ = 'p'; | |
821 | if (chptr->mode.mode & MODE_MODERATED) | |
822 | *mbuf++ = 'm'; | |
823 | if (chptr->mode.mode & MODE_TOPICLIMIT) | |
824 | *mbuf++ = 't'; | |
825 | if (chptr->mode.mode & MODE_INVITEONLY) | |
826 | *mbuf++ = 'i'; | |
827 | if (chptr->mode.mode & MODE_NOPRIVMSGS) | |
828 | *mbuf++ = 'n'; | |
829 | if (chptr->mode.mode & MODE_REGONLY) | |
830 | *mbuf++ = 'r'; | |
d8e74551 | 831 | if (chptr->mode.mode & MODE_NOCOLOUR) |
832 | *mbuf++ = 'c'; | |
833 | if (chptr->mode.mode & MODE_NOCTCP) | |
834 | *mbuf++ = 'C'; | |
835 | if (chptr->mode.mode & MODE_NONOTICE) | |
836 | *mbuf++ = 'N'; | |
837 | if (chptr->mode.mode & MODE_NOQUITPARTS) | |
838 | *mbuf++ = 'u'; | |
189935b1 | 839 | if (chptr->mode.mode & MODE_DELJOINS) |
840 | *mbuf++ = 'D'; | |
3ba38331 | 841 | if (chptr->mode.mode & MODE_NOMULTITARGET) |
1cade670 | 842 | *mbuf++ = 'T'; |
843 | if (chptr->mode.mode & MODE_MODERATENOREG) | |
3ba38331 | 844 | *mbuf++ = 'M'; |
189935b1 | 845 | else if (MyUser(cptr) && (chptr->mode.mode & MODE_WASDELJOINS)) |
846 | *mbuf++ = 'd'; | |
847 | if (chptr->mode.limit) { | |
848 | *mbuf++ = 'l'; | |
849 | ircd_snprintf(0, pbuf, buflen, "%u", chptr->mode.limit); | |
850 | previous_parameter = 1; | |
851 | } | |
852 | ||
853 | if (*chptr->mode.key) { | |
854 | *mbuf++ = 'k'; | |
855 | if (previous_parameter) | |
856 | strcat(pbuf, " "); | |
d8e74551 | 857 | if (is_chan_op(cptr, chptr) || IsServer(cptr) || IsOper(cptr)) { |
189935b1 | 858 | strcat(pbuf, chptr->mode.key); |
859 | } else | |
860 | strcat(pbuf, "*"); | |
861 | previous_parameter = 1; | |
862 | } | |
863 | if (*chptr->mode.apass) { | |
864 | *mbuf++ = 'A'; | |
865 | if (previous_parameter) | |
866 | strcat(pbuf, " "); | |
867 | if (IsServer(cptr)) { | |
868 | strcat(pbuf, chptr->mode.apass); | |
869 | } else | |
870 | strcat(pbuf, "*"); | |
871 | previous_parameter = 1; | |
872 | } | |
873 | if (*chptr->mode.upass) { | |
874 | *mbuf++ = 'U'; | |
875 | if (previous_parameter) | |
876 | strcat(pbuf, " "); | |
877 | if (IsServer(cptr) || (member && IsChanOp(member) && OpLevel(member) == 0)) { | |
878 | strcat(pbuf, chptr->mode.upass); | |
879 | } else | |
880 | strcat(pbuf, "*"); | |
881 | } | |
882 | *mbuf = '\0'; | |
883 | } | |
884 | ||
885 | /** Compare two members oplevel | |
886 | * | |
887 | * @param mp1 Pointer to a pointer to a membership | |
888 | * @param mp2 Pointer to a pointer to a membership | |
889 | * | |
890 | * @returns 0 if equal, -1 if mp1 is lower, +1 otherwise. | |
891 | * | |
892 | * Used for qsort(3). | |
893 | */ | |
894 | int compare_member_oplevel(const void *mp1, const void *mp2) | |
895 | { | |
896 | struct Membership const* member1 = *(struct Membership const**)mp1; | |
897 | struct Membership const* member2 = *(struct Membership const**)mp2; | |
898 | if (member1->oplevel == member2->oplevel) | |
899 | return 0; | |
900 | return (member1->oplevel < member2->oplevel) ? -1 : 1; | |
901 | } | |
902 | ||
903 | /* send "cptr" a full list of the modes for channel chptr. | |
904 | * | |
905 | * Sends a BURST line to cptr, bursting all the modes for the channel. | |
906 | * | |
907 | * @param cptr Client pointer | |
908 | * @param chptr Channel pointer | |
909 | */ | |
910 | void send_channel_modes(struct Client *cptr, struct Channel *chptr) | |
911 | { | |
912 | /* The order in which modes are generated is now mandatory */ | |
913 | static unsigned int current_flags[4] = | |
914 | { 0, CHFL_VOICE, CHFL_CHANOP, CHFL_CHANOP | CHFL_VOICE }; | |
915 | int first = 1; | |
916 | int full = 1; | |
917 | int flag_cnt = 0; | |
918 | int new_mode = 0; | |
919 | size_t len; | |
920 | struct Membership* member; | |
921 | struct Ban* lp2; | |
922 | char modebuf[MODEBUFLEN]; | |
923 | char parabuf[MODEBUFLEN]; | |
924 | struct MsgBuf *mb; | |
925 | int number_of_ops = 0; | |
926 | int opped_members_index = 0; | |
927 | struct Membership** opped_members = NULL; | |
928 | int last_oplevel = 0; | |
0c466275 | 929 | int send_oplevels = 0; |
189935b1 | 930 | |
931 | assert(0 != cptr); | |
932 | assert(0 != chptr); | |
933 | ||
934 | if (IsLocalChannel(chptr->chname)) | |
935 | return; | |
936 | ||
937 | member = chptr->members; | |
938 | lp2 = chptr->banlist; | |
939 | ||
940 | *modebuf = *parabuf = '\0'; | |
941 | channel_modes(cptr, modebuf, parabuf, sizeof(parabuf), chptr, 0); | |
942 | ||
943 | for (first = 1; full; first = 0) /* Loop for multiple messages */ | |
944 | { | |
945 | full = 0; /* Assume by default we get it | |
946 | all in one message */ | |
947 | ||
948 | /* (Continued) prefix: "<Y> B <channel> <TS>" */ | |
949 | /* is there any better way we can do this? */ | |
950 | mb = msgq_make(&me, "%C " TOK_BURST " %H %Tu", &me, chptr, | |
951 | chptr->creationtime); | |
952 | ||
953 | if (first && modebuf[1]) /* Add simple modes (Aiklmnpstu) | |
954 | if first message */ | |
955 | { | |
956 | /* prefix: "<Y> B <channel> <TS>[ <modes>[ <params>]]" */ | |
957 | msgq_append(&me, mb, " %s", modebuf); | |
958 | ||
959 | if (*parabuf) | |
960 | msgq_append(&me, mb, " %s", parabuf); | |
961 | } | |
962 | ||
963 | /* | |
964 | * Attach nicks, comma separated " nick[:modes],nick[:modes],..." | |
965 | * | |
966 | * First find all opless members. | |
967 | * Run 2 times over all members, to group the members with | |
968 | * and without voice together. | |
969 | * Then run 2 times over all opped members (which are ordered | |
970 | * by op-level) to also group voice and non-voice together. | |
971 | */ | |
972 | for (first = 1; flag_cnt < 4; new_mode = 1, ++flag_cnt) | |
973 | { | |
974 | while (member) | |
975 | { | |
976 | if (flag_cnt < 2 && IsChanOp(member)) | |
977 | { | |
978 | /* | |
979 | * The first loop (to find all non-voice/op), we count the ops. | |
980 | * The second loop (to find all voiced non-ops), store the ops | |
981 | * in a dynamic array. | |
982 | */ | |
983 | if (flag_cnt == 0) | |
984 | ++number_of_ops; | |
985 | else | |
986 | opped_members[opped_members_index++] = member; | |
0c466275 | 987 | /* We also send oplevels if anyone is below the weakest level. */ |
988 | if (OpLevel(member) < MAXOPLEVEL) | |
989 | send_oplevels = 1; | |
189935b1 | 990 | } |
991 | /* Only handle the members with the flags that we are interested in. */ | |
992 | if ((member->status & CHFL_VOICED_OR_OPPED) == current_flags[flag_cnt]) | |
993 | { | |
994 | if (msgq_bufleft(mb) < NUMNICKLEN + 3 + MAXOPLEVELDIGITS) | |
995 | /* The 3 + MAXOPLEVELDIGITS is a possible ",:v999". */ | |
996 | { | |
997 | full = 1; /* Make sure we continue after | |
998 | sending it so far */ | |
999 | /* Ensure the new BURST line contains the current | |
1000 | * ":mode", except when there is no mode yet. */ | |
1001 | new_mode = (flag_cnt > 0) ? 1 : 0; | |
1002 | break; /* Do not add this member to this message */ | |
1003 | } | |
1004 | msgq_append(&me, mb, "%c%C", first ? ' ' : ',', member->user); | |
1005 | first = 0; /* From now on, use commas to add new nicks */ | |
1006 | ||
1007 | /* | |
1008 | * Do we have a nick with a new mode ? | |
1009 | * Or are we starting a new BURST line? | |
1010 | */ | |
1011 | if (new_mode) | |
1012 | { | |
1013 | /* | |
1014 | * This means we are at the _first_ member that has only | |
1015 | * voice, or the first member that has only ops, or the | |
1016 | * first member that has voice and ops (so we get here | |
1017 | * at most three times, plus once for every start of | |
1018 | * a continued BURST line where only these modes is current. | |
1019 | * In the two cases where the current mode includes ops, | |
1020 | * we need to add the _absolute_ value of the oplevel to the mode. | |
1021 | */ | |
1022 | char tbuf[3 + MAXOPLEVELDIGITS] = ":"; | |
1023 | int loc = 1; | |
1024 | ||
1025 | if (HasVoice(member)) /* flag_cnt == 1 or 3 */ | |
1026 | tbuf[loc++] = 'v'; | |
1027 | if (IsChanOp(member)) /* flag_cnt == 2 or 3 */ | |
1028 | { | |
1029 | /* append the absolute value of the oplevel */ | |
0c466275 | 1030 | if (send_oplevels) |
189935b1 | 1031 | loc += ircd_snprintf(0, tbuf + loc, sizeof(tbuf) - loc, "%u", last_oplevel = member->oplevel); |
1032 | else | |
1033 | tbuf[loc++] = 'o'; | |
1034 | } | |
1035 | tbuf[loc] = '\0'; | |
1036 | msgq_append(&me, mb, tbuf); | |
1037 | new_mode = 0; | |
1038 | } | |
0c466275 | 1039 | else if (send_oplevels && flag_cnt > 1 && last_oplevel != member->oplevel) |
189935b1 | 1040 | { |
1041 | /* | |
1042 | * This can't be the first member of a (continued) BURST | |
1043 | * message because then either flag_cnt == 0 or new_mode == 1 | |
1044 | * Now we need to append the incremental value of the oplevel. | |
1045 | */ | |
1046 | char tbuf[2 + MAXOPLEVELDIGITS]; | |
1047 | ircd_snprintf(0, tbuf, sizeof(tbuf), ":%u", member->oplevel - last_oplevel); | |
1048 | last_oplevel = member->oplevel; | |
1049 | msgq_append(&me, mb, tbuf); | |
1050 | } | |
1051 | } | |
1052 | /* Go to the next `member'. */ | |
1053 | if (flag_cnt < 2) | |
1054 | member = member->next_member; | |
1055 | else | |
1056 | member = opped_members[++opped_members_index]; | |
1057 | } | |
1058 | if (full) | |
1059 | break; | |
1060 | ||
1061 | /* Point `member' at the start of the list again. */ | |
1062 | if (flag_cnt == 0) | |
1063 | { | |
1064 | member = chptr->members; | |
1065 | /* Now, after one loop, we know the number of ops and can | |
1066 | * allocate the dynamic array with pointer to the ops. */ | |
1067 | opped_members = (struct Membership**) | |
1068 | MyMalloc((number_of_ops + 1) * sizeof(struct Membership*)); | |
1069 | opped_members[number_of_ops] = NULL; /* Needed for loop termination */ | |
1070 | } | |
1071 | else | |
1072 | { | |
1073 | /* At the end of the second loop, sort the opped members with | |
1074 | * increasing op-level, so that we will output them in the | |
1075 | * correct order (and all op-level increments stay positive) */ | |
1076 | if (flag_cnt == 1) | |
1077 | qsort(opped_members, number_of_ops, | |
1078 | sizeof(struct Membership*), compare_member_oplevel); | |
1079 | /* The third and fourth loop run only over the opped members. */ | |
1080 | member = opped_members[(opped_members_index = 0)]; | |
1081 | } | |
1082 | ||
1083 | } /* loop over 0,+v,+o,+ov */ | |
1084 | ||
1085 | if (!full) | |
1086 | { | |
1087 | /* Attach all bans, space separated " :%ban ban ..." */ | |
1088 | for (first = 2; lp2; lp2 = lp2->next) | |
1089 | { | |
1090 | len = strlen(lp2->banstr); | |
1091 | if (msgq_bufleft(mb) < len + 1 + first) | |
1092 | /* The +1 stands for the added ' '. | |
1093 | * The +first stands for the added ":%". | |
1094 | */ | |
1095 | { | |
1096 | full = 1; | |
1097 | break; | |
1098 | } | |
1099 | msgq_append(&me, mb, " %s%s", first ? ":%" : "", | |
1100 | lp2->banstr); | |
1101 | first = 0; | |
1102 | } | |
1103 | } | |
1104 | ||
1105 | send_buffer(cptr, mb, 0); /* Send this message */ | |
1106 | msgq_clean(mb); | |
1107 | } /* Continue when there was something | |
1108 | that didn't fit (full==1) */ | |
1109 | if (opped_members) | |
1110 | MyFree(opped_members); | |
1111 | if (feature_bool(FEAT_TOPIC_BURST) && (chptr->topic[0] != '\0')) | |
1112 | sendcmdto_one(&me, CMD_TOPIC, cptr, "%H %Tu %Tu :%s", chptr, | |
1113 | chptr->creationtime, chptr->topic_time, chptr->topic); | |
1114 | } | |
1115 | ||
1116 | /** Canonify a mask. | |
1117 | * pretty_mask | |
1118 | * | |
1119 | * @author Carlo Wood (Run), | |
1120 | * 05 Oct 1998. | |
1121 | * | |
1122 | * When the nick is longer then NICKLEN, it is cut off (its an error of course). | |
1123 | * When the user name or host name are too long (USERLEN and HOSTLEN | |
1124 | * respectively) then they are cut off at the start with a '*'. | |
1125 | * | |
1126 | * The following transformations are made: | |
1127 | * | |
1128 | * 1) xxx -> nick!*@* | |
1129 | * 2) xxx.xxx -> *!*\@host | |
1130 | * 3) xxx\!yyy -> nick!user\@* | |
1131 | * 4) xxx\@yyy -> *!user\@host | |
1132 | * 5) xxx!yyy\@zzz -> nick!user\@host | |
1133 | * | |
1134 | * @param mask The uncanonified mask. | |
1135 | * @returns The updated mask in a static buffer. | |
1136 | */ | |
1137 | char *pretty_mask(char *mask) | |
1138 | { | |
1139 | static char star[2] = { '*', 0 }; | |
1140 | static char retmask[NICKLEN + USERLEN + HOSTLEN + 3]; | |
1141 | char *last_dot = NULL; | |
1142 | char *ptr; | |
1143 | ||
1144 | /* Case 1: default */ | |
1145 | char *nick = mask; | |
1146 | char *user = star; | |
1147 | char *host = star; | |
1148 | ||
1149 | /* Do a _single_ pass through the characters of the mask: */ | |
1150 | for (ptr = mask; *ptr; ++ptr) | |
1151 | { | |
1152 | if (*ptr == '!') | |
1153 | { | |
1154 | /* Case 3 or 5: Found first '!' (without finding a '@' yet) */ | |
1155 | user = ++ptr; | |
1156 | host = star; | |
1157 | } | |
1158 | else if (*ptr == '@') | |
1159 | { | |
1160 | /* Case 4: Found last '@' (without finding a '!' yet) */ | |
1161 | nick = star; | |
1162 | user = mask; | |
1163 | host = ++ptr; | |
1164 | } | |
1165 | else if (*ptr == '.' || *ptr == ':') | |
1166 | { | |
1167 | /* Case 2: Found character specific to IP or hostname (without | |
1168 | * finding a '!' or '@' yet) */ | |
1169 | last_dot = ptr; | |
1170 | continue; | |
1171 | } | |
1172 | else | |
1173 | continue; | |
1174 | for (; *ptr; ++ptr) | |
1175 | { | |
1176 | if (*ptr == '@') | |
1177 | { | |
1178 | /* Case 4 or 5: Found last '@' */ | |
1179 | host = ptr + 1; | |
1180 | } | |
1181 | } | |
1182 | break; | |
1183 | } | |
1184 | if (user == star && last_dot) | |
1185 | { | |
1186 | /* Case 2: */ | |
1187 | nick = star; | |
1188 | user = star; | |
1189 | host = mask; | |
1190 | } | |
1191 | /* Check lengths */ | |
1192 | if (nick != star) | |
1193 | { | |
1194 | char *nick_end = (user != star) ? user - 1 : ptr; | |
1195 | if (nick_end - nick > NICKLEN) | |
1196 | nick[NICKLEN] = 0; | |
1197 | *nick_end = 0; | |
1198 | } | |
1199 | if (user != star) | |
1200 | { | |
1201 | char *user_end = (host != star) ? host - 1 : ptr; | |
1202 | if (user_end - user > USERLEN) | |
1203 | { | |
1204 | user = user_end - USERLEN; | |
1205 | *user = '*'; | |
1206 | } | |
1207 | *user_end = 0; | |
1208 | } | |
1209 | if (host != star && ptr - host > HOSTLEN) | |
1210 | { | |
1211 | host = ptr - HOSTLEN; | |
1212 | *host = '*'; | |
1213 | } | |
1214 | ircd_snprintf(0, retmask, sizeof(retmask), "%s!%s@%s", nick, user, host); | |
1215 | return retmask; | |
1216 | } | |
1217 | ||
1218 | /** send a banlist to a client for a channel | |
1219 | * | |
1220 | * @param cptr Client to send the banlist to. | |
1221 | * @param chptr Channel whose banlist to send. | |
1222 | */ | |
1223 | static void send_ban_list(struct Client* cptr, struct Channel* chptr) | |
1224 | { | |
1225 | struct Ban* lp; | |
1226 | ||
1227 | assert(0 != cptr); | |
1228 | assert(0 != chptr); | |
1229 | ||
1230 | for (lp = chptr->banlist; lp; lp = lp->next) | |
1231 | send_reply(cptr, RPL_BANLIST, chptr->chname, lp->banstr, | |
1232 | lp->who, lp->when); | |
1233 | ||
1234 | send_reply(cptr, RPL_ENDOFBANLIST, chptr->chname); | |
1235 | } | |
1236 | ||
1237 | /** Remove bells and commas from channel name | |
1238 | * | |
1239 | * @param cn Channel name to clean, modified in place. | |
1240 | */ | |
1241 | void clean_channelname(char *cn) | |
1242 | { | |
1243 | int i; | |
1244 | ||
1245 | for (i = 0; cn[i]; i++) { | |
1246 | if (i >= IRCD_MIN(CHANNELLEN, feature_int(FEAT_CHANNELLEN)) | |
1247 | || !IsChannelChar(cn[i])) { | |
1248 | cn[i] = '\0'; | |
1249 | return; | |
1250 | } | |
1251 | if (IsChannelLower(cn[i])) { | |
1252 | cn[i] = ToLower(cn[i]); | |
1253 | #ifndef FIXME | |
1254 | /* | |
1255 | * Remove for .08+ | |
1256 | * toupper(0xd0) | |
1257 | */ | |
1258 | if ((unsigned char)(cn[i]) == 0xd0) | |
1259 | cn[i] = (char) 0xf0; | |
1260 | #endif | |
1261 | } | |
1262 | } | |
1263 | } | |
1264 | ||
1265 | /** Get a channel block, creating if necessary. | |
1266 | * Get Channel block for chname (and allocate a new channel | |
1267 | * block, if it didn't exists before). | |
1268 | * | |
1269 | * @param cptr Client joining the channel. | |
1270 | * @param chname The name of the channel to join. | |
1271 | * @param flag set to CGT_CREATE to create the channel if it doesn't | |
1272 | * exist | |
1273 | * | |
1274 | * @returns NULL if the channel is invalid, doesn't exist and CGT_CREATE | |
1275 | * wasn't specified or a pointer to the channel structure | |
1276 | */ | |
1277 | struct Channel *get_channel(struct Client *cptr, char *chname, ChannelGetType flag) | |
1278 | { | |
1279 | struct Channel *chptr; | |
1280 | int len; | |
1281 | ||
1282 | if (EmptyString(chname)) | |
1283 | return NULL; | |
1284 | ||
1285 | len = strlen(chname); | |
1286 | if (MyUser(cptr) && len > CHANNELLEN) | |
1287 | { | |
1288 | len = CHANNELLEN; | |
1289 | *(chname + CHANNELLEN) = '\0'; | |
1290 | } | |
1291 | if ((chptr = FindChannel(chname))) | |
1292 | return (chptr); | |
1293 | if (flag == CGT_CREATE) | |
1294 | { | |
1295 | chptr = (struct Channel*) MyMalloc(sizeof(struct Channel) + len); | |
1296 | assert(0 != chptr); | |
1297 | ++UserStats.channels; | |
1298 | memset(chptr, 0, sizeof(struct Channel)); | |
1299 | strcpy(chptr->chname, chname); | |
1300 | if (GlobalChannelList) | |
1301 | GlobalChannelList->prev = chptr; | |
1302 | chptr->prev = NULL; | |
1303 | chptr->next = GlobalChannelList; | |
1304 | chptr->creationtime = MyUser(cptr) ? TStime() : (time_t) 0; | |
1305 | GlobalChannelList = chptr; | |
1306 | hAddChannel(chptr); | |
1307 | } | |
1308 | return chptr; | |
1309 | } | |
1310 | ||
d8e74551 | 1311 | int SetAutoChanModes(struct Channel *chptr) |
1312 | { | |
1313 | static int chan_flags[] = { | |
1314 | MODE_PRIVATE, 'p', | |
1315 | MODE_SECRET, 's', | |
1316 | MODE_MODERATED, 'm', | |
1317 | MODE_TOPICLIMIT, 't', | |
1318 | MODE_INVITEONLY, 'i', | |
1319 | MODE_NOPRIVMSGS, 'n', | |
1320 | MODE_REGONLY, 'r', | |
1321 | MODE_NOCOLOUR, 'c', | |
1322 | MODE_NOCTCP, 'C', | |
1323 | MODE_NONOTICE, 'N', | |
1324 | MODE_DELJOINS, 'D', | |
3ba38331 | 1325 | MODE_NOQUITPARTS, 'u', |
1cade670 | 1326 | MODE_NOMULTITARGET, 'T', |
1327 | MODE_MODERATENOREG, 'M' | |
d8e74551 | 1328 | }; |
1329 | ||
1330 | unsigned int *flag_p; | |
1331 | unsigned int t_mode; | |
1332 | const char *modestr; | |
1333 | ||
1334 | t_mode = 0; | |
1335 | ||
1336 | assert(0 != chptr); | |
1337 | ||
1338 | if (!feature_bool(FEAT_AUTOCHANMODES) || !feature_str(FEAT_AUTOCHANMODES_LIST) || strlen(feature_str(FEAT_AUTOCHANMODES_LIST)) <= 1) | |
1339 | return(-1); | |
1340 | ||
1341 | modestr = feature_str(FEAT_AUTOCHANMODES_LIST); | |
1342 | ||
1343 | for (; *modestr; modestr++) { | |
1344 | for (flag_p = chan_flags; flag_p[0]; flag_p += 2) /* look up flag */ | |
1345 | if (flag_p[1] == *modestr) | |
1346 | break; | |
1347 | ||
1348 | if (!flag_p[0]) /* didn't find it */ | |
1349 | continue; | |
1350 | ||
1351 | t_mode |= flag_p[0]; | |
1352 | ||
1353 | } /* for (; *modestr; modestr++) { */ | |
1354 | ||
1355 | if (t_mode != 0) | |
1356 | chptr->mode.mode = t_mode; | |
1357 | return(0); | |
1358 | } | |
1359 | ||
189935b1 | 1360 | /** invite a user to a channel. |
1361 | * | |
1362 | * Adds an invite for a user to a channel. Limits the number of invites | |
1363 | * to FEAT_MAXCHANNELSPERUSER. Does not sent notification to the user. | |
1364 | * | |
1365 | * @param cptr The client to be invited. | |
1366 | * @param chptr The channel to be invited to. | |
1367 | */ | |
1368 | void add_invite(struct Client *cptr, struct Channel *chptr) | |
1369 | { | |
1370 | struct SLink *inv, **tmp; | |
1371 | ||
1372 | del_invite(cptr, chptr); | |
1373 | /* | |
1374 | * Delete last link in chain if the list is max length | |
1375 | */ | |
1376 | assert(list_length((cli_user(cptr))->invited) == (cli_user(cptr))->invites); | |
1377 | if ((cli_user(cptr))->invites >= feature_int(FEAT_MAXCHANNELSPERUSER)) | |
1378 | del_invite(cptr, (cli_user(cptr))->invited->value.chptr); | |
1379 | /* | |
1380 | * Add client to channel invite list | |
1381 | */ | |
1382 | inv = make_link(); | |
1383 | inv->value.cptr = cptr; | |
1384 | inv->next = chptr->invites; | |
1385 | chptr->invites = inv; | |
1386 | /* | |
1387 | * Add channel to the end of the client invite list | |
1388 | */ | |
1389 | for (tmp = &((cli_user(cptr))->invited); *tmp; tmp = &((*tmp)->next)); | |
1390 | inv = make_link(); | |
1391 | inv->value.chptr = chptr; | |
1392 | inv->next = NULL; | |
1393 | (*tmp) = inv; | |
1394 | (cli_user(cptr))->invites++; | |
1395 | } | |
1396 | ||
1397 | /** Delete an invite | |
1398 | * Delete Invite block from channel invite list and client invite list | |
1399 | * | |
1400 | * @param cptr Client pointer | |
1401 | * @param chptr Channel pointer | |
1402 | */ | |
1403 | void del_invite(struct Client *cptr, struct Channel *chptr) | |
1404 | { | |
1405 | struct SLink **inv, *tmp; | |
1406 | ||
1407 | for (inv = &(chptr->invites); (tmp = *inv); inv = &tmp->next) | |
1408 | if (tmp->value.cptr == cptr) | |
1409 | { | |
1410 | *inv = tmp->next; | |
1411 | free_link(tmp); | |
1412 | tmp = 0; | |
1413 | (cli_user(cptr))->invites--; | |
1414 | break; | |
1415 | } | |
1416 | ||
1417 | for (inv = &((cli_user(cptr))->invited); (tmp = *inv); inv = &tmp->next) | |
1418 | if (tmp->value.chptr == chptr) | |
1419 | { | |
1420 | *inv = tmp->next; | |
1421 | free_link(tmp); | |
1422 | tmp = 0; | |
1423 | break; | |
1424 | } | |
1425 | } | |
1426 | ||
1427 | /** @page zombie Explanation of Zombies | |
1428 | * | |
1429 | * Synopsis: | |
1430 | * | |
1431 | * A channel member is turned into a zombie when he is kicked from a | |
1432 | * channel but his server has not acknowledged the kick. Servers that | |
1433 | * see the member as a zombie can accept actions he performed before | |
1434 | * being kicked, without allowing chanop operations from outsiders or | |
1435 | * desyncing the network. | |
1436 | * | |
1437 | * Consider: | |
1438 | * <pre> | |
1439 | * client | |
1440 | * | | |
1441 | * c | |
1442 | * | | |
1443 | * X --a--> A --b--> B --d--> D | |
1444 | * | | |
1445 | * who | |
1446 | * </pre> | |
1447 | * | |
1448 | * Where `who' is being KICK-ed by a "KICK" message received by server 'A' | |
1449 | * via 'a', or on server 'B' via either 'b' or 'c', or on server D via 'd'. | |
1450 | * | |
1451 | * a) On server A : set CHFL_ZOMBIE for `who' (lp) and pass on the KICK. | |
1452 | * Remove the user immediately when no users are left on the channel. | |
1453 | * b) On server B : remove the user (who/lp) from the channel, send a | |
1454 | * PART upstream (to A) and pass on the KICK. | |
1455 | * c) KICKed by `client'; On server B : remove the user (who/lp) from the | |
1456 | * channel, and pass on the KICK. | |
1457 | * d) On server D : remove the user (who/lp) from the channel, and pass on | |
1458 | * the KICK. | |
1459 | * | |
1460 | * Note: | |
1461 | * - Setting the ZOMBIE flag never hurts, we either remove the | |
1462 | * client after that or we don't. | |
1463 | * - The KICK message was already passed on, as should be in all cases. | |
1464 | * - `who' is removed in all cases except case a) when users are left. | |
1465 | * - A PART is only sent upstream in case b). | |
1466 | * | |
1467 | * 2 aug 97: | |
1468 | * <pre> | |
1469 | * 6 | |
1470 | * | | |
1471 | * 1 --- 2 --- 3 --- 4 --- 5 | |
1472 | * | | | |
1473 | * kicker who | |
1474 | * </pre> | |
1475 | * | |
1476 | * We also need to turn 'who' into a zombie on servers 1 and 6, | |
1477 | * because a KICK from 'who' (kicking someone else in that direction) | |
1478 | * can arrive there afterward - which should not be bounced itself. | |
1479 | * Therefore case a) also applies for servers 1 and 6. | |
1480 | * | |
1481 | * --Run | |
1482 | */ | |
1483 | ||
1484 | /** Turn a user on a channel into a zombie | |
1485 | * This function turns a user into a zombie (see \ref zombie) | |
1486 | * | |
1487 | * @param member The structure representing this user on this channel. | |
1488 | * @param who The client that is being kicked. | |
1489 | * @param cptr The connection the kick came from. | |
1490 | * @param sptr The client that is doing the kicking. | |
1491 | * @param chptr The channel the user is being kicked from. | |
1492 | */ | |
1493 | void make_zombie(struct Membership* member, struct Client* who, | |
1494 | struct Client* cptr, struct Client* sptr, struct Channel* chptr) | |
1495 | { | |
1496 | assert(0 != member); | |
1497 | assert(0 != who); | |
1498 | assert(0 != cptr); | |
1499 | assert(0 != chptr); | |
1500 | ||
1501 | /* Default for case a): */ | |
1502 | SetZombie(member); | |
1503 | ||
1504 | /* Case b) or c) ?: */ | |
1505 | if (MyUser(who)) /* server 4 */ | |
1506 | { | |
1507 | if (IsServer(cptr)) /* Case b) ? */ | |
1508 | sendcmdto_one(who, CMD_PART, cptr, "%H", chptr); | |
1509 | remove_user_from_channel(who, chptr); | |
1510 | return; | |
1511 | } | |
1512 | if (cli_from(who) == cptr) /* True on servers 1, 5 and 6 */ | |
1513 | { | |
1514 | struct Client *acptr = IsServer(sptr) ? sptr : (cli_user(sptr))->server; | |
1515 | for (; acptr != &me; acptr = (cli_serv(acptr))->up) | |
1516 | if (acptr == (cli_user(who))->server) /* Case d) (server 5) */ | |
1517 | { | |
1518 | remove_user_from_channel(who, chptr); | |
1519 | return; | |
1520 | } | |
1521 | } | |
1522 | ||
1523 | /* Case a) (servers 1, 2, 3 and 6) */ | |
1524 | if (channel_all_zombies(chptr)) | |
1525 | remove_user_from_channel(who, chptr); | |
1526 | ||
1527 | /* XXX Can't actually call Debug here; if the channel is all zombies, | |
1528 | * chptr will no longer exist when we get here. | |
1529 | Debug((DEBUG_INFO, "%s is now a zombie on %s", who->name, chptr->chname)); | |
1530 | */ | |
1531 | } | |
1532 | ||
1533 | /** returns the number of zombies on a channel | |
1534 | * @param chptr Channel to count zombies in. | |
1535 | * | |
1536 | * @returns The number of zombies on the channel. | |
1537 | */ | |
1538 | int number_of_zombies(struct Channel *chptr) | |
1539 | { | |
1540 | struct Membership* member; | |
1541 | int count = 0; | |
1542 | ||
1543 | assert(0 != chptr); | |
1544 | for (member = chptr->members; member; member = member->next_member) { | |
1545 | if (IsZombie(member)) | |
1546 | ++count; | |
1547 | } | |
1548 | return count; | |
1549 | } | |
1550 | ||
1551 | /** Concatenate some strings together. | |
1552 | * This helper function builds an argument string in strptr, consisting | |
1553 | * of the original string, a space, and str1 and str2 concatenated (if, | |
1554 | * of course, str2 is not NULL) | |
1555 | * | |
1556 | * @param strptr The buffer to concatenate into | |
1557 | * @param strptr_i modified offset to the position to modify | |
1558 | * @param str1 The string to concatenate from. | |
1559 | * @param str2 The second string to contatenate from. | |
1560 | * @param c Charactor to separate the string from str1 and str2. | |
1561 | */ | |
1562 | static void | |
1563 | build_string(char *strptr, int *strptr_i, const char *str1, | |
1564 | const char *str2, char c) | |
1565 | { | |
1566 | if (c) | |
1567 | strptr[(*strptr_i)++] = c; | |
1568 | ||
1569 | while (*str1) | |
1570 | strptr[(*strptr_i)++] = *(str1++); | |
1571 | ||
1572 | if (str2) | |
1573 | while (*str2) | |
1574 | strptr[(*strptr_i)++] = *(str2++); | |
1575 | ||
1576 | strptr[(*strptr_i)] = '\0'; | |
1577 | } | |
1578 | ||
1579 | /** Flush out the modes | |
1580 | * This is the workhorse of our ModeBuf suite; this actually generates the | |
1581 | * output MODE commands, HACK notices, or whatever. It's pretty complicated. | |
1582 | * | |
1583 | * @param mbuf The mode buffer to flush | |
1584 | * @param all If true, flush all modes, otherwise leave partial modes in the | |
1585 | * buffer. | |
1586 | * | |
1587 | * @returns 0 | |
1588 | */ | |
1589 | static int | |
1590 | modebuf_flush_int(struct ModeBuf *mbuf, int all) | |
1591 | { | |
1592 | /* we only need the flags that don't take args right now */ | |
1593 | static int flags[] = { | |
1594 | /* MODE_CHANOP, 'o', */ | |
1595 | /* MODE_VOICE, 'v', */ | |
1596 | MODE_PRIVATE, 'p', | |
1597 | MODE_SECRET, 's', | |
1598 | MODE_MODERATED, 'm', | |
1599 | MODE_TOPICLIMIT, 't', | |
1600 | MODE_INVITEONLY, 'i', | |
1601 | MODE_NOPRIVMSGS, 'n', | |
1602 | MODE_REGONLY, 'r', | |
1603 | MODE_DELJOINS, 'D', | |
189935b1 | 1604 | /* MODE_KEY, 'k', */ |
1605 | /* MODE_BAN, 'b', */ | |
1606 | MODE_LIMIT, 'l', | |
1607 | /* MODE_APASS, 'A', */ | |
1608 | /* MODE_UPASS, 'U', */ | |
d8e74551 | 1609 | MODE_NOQUITPARTS, 'u', |
1610 | MODE_NOCOLOUR, 'c', | |
1611 | MODE_NOCTCP, 'C', | |
1612 | MODE_NONOTICE, 'N', | |
1cade670 | 1613 | MODE_NOMULTITARGET, 'T', |
1614 | MODE_MODERATENOREG, 'M', | |
189935b1 | 1615 | 0x0, 0x0 |
1616 | }; | |
052b069e | 1617 | static int local_flags[] = { |
1618 | MODE_WASDELJOINS, 'd', | |
1619 | 0x0, 0x0 | |
1620 | }; | |
189935b1 | 1621 | int i; |
1622 | int *flag_p; | |
1623 | ||
1624 | struct Client *app_source; /* where the MODE appears to come from */ | |
1625 | ||
052b069e | 1626 | char addbuf[20], addbuf_local[20]; /* accumulates +psmtin, etc. */ |
1627 | int addbuf_i = 0, addbuf_local_i = 0; | |
1628 | char rembuf[20], rembuf_local[20]; /* accumulates -psmtin, etc. */ | |
1629 | int rembuf_i = 0, rembuf_local_i = 0; | |
189935b1 | 1630 | char *bufptr; /* we make use of indirection to simplify the code */ |
1631 | int *bufptr_i; | |
1632 | ||
1633 | char addstr[BUFSIZE]; /* accumulates MODE parameters to add */ | |
1634 | int addstr_i; | |
1635 | char remstr[BUFSIZE]; /* accumulates MODE parameters to remove */ | |
1636 | int remstr_i; | |
1637 | char *strptr; /* more indirection to simplify the code */ | |
1638 | int *strptr_i; | |
1639 | ||
1640 | int totalbuflen = BUFSIZE - 200; /* fuzz factor -- don't overrun buffer! */ | |
1641 | int tmp; | |
1642 | ||
1643 | char limitbuf[20]; /* convert limits to strings */ | |
1644 | ||
1645 | unsigned int limitdel = MODE_LIMIT; | |
1646 | ||
1647 | assert(0 != mbuf); | |
1648 | ||
1649 | /* If the ModeBuf is empty, we have nothing to do */ | |
1650 | if (mbuf->mb_add == 0 && mbuf->mb_rem == 0 && mbuf->mb_count == 0) | |
1651 | return 0; | |
1652 | ||
1653 | /* Ok, if we were given the OPMODE flag, or its a server, hide the source. | |
1654 | */ | |
052b069e | 1655 | if (feature_bool(FEAT_HIS_MODEWHO) && |
1656 | (mbuf->mb_dest & MODEBUF_DEST_OPMODE || | |
1657 | IsServer(mbuf->mb_source) || | |
1658 | IsMe(mbuf->mb_source))) | |
189935b1 | 1659 | app_source = &his; |
1660 | else | |
1661 | app_source = mbuf->mb_source; | |
1662 | ||
1663 | /* | |
1664 | * Account for user we're bouncing; we have to get it in on the first | |
1665 | * bounced MODE, or we could have problems | |
1666 | */ | |
1667 | if (mbuf->mb_dest & MODEBUF_DEST_DEOP) | |
1668 | totalbuflen -= 6; /* numeric nick == 5, plus one space */ | |
1669 | ||
1670 | /* Calculate the simple flags */ | |
1671 | for (flag_p = flags; flag_p[0]; flag_p += 2) { | |
1672 | if (*flag_p & mbuf->mb_add) | |
1673 | addbuf[addbuf_i++] = flag_p[1]; | |
1674 | else if (*flag_p & mbuf->mb_rem) | |
1675 | rembuf[rembuf_i++] = flag_p[1]; | |
1676 | } | |
1677 | ||
052b069e | 1678 | /* Some flags may be for local display only. */ |
1679 | for (flag_p = local_flags; flag_p[0]; flag_p += 2) { | |
1680 | if (*flag_p & mbuf->mb_add) | |
1681 | addbuf_local[addbuf_local_i++] = flag_p[1]; | |
1682 | else if (*flag_p & mbuf->mb_rem) | |
1683 | rembuf_local[rembuf_local_i++] = flag_p[1]; | |
1684 | } | |
1685 | ||
189935b1 | 1686 | /* Now go through the modes with arguments... */ |
1687 | for (i = 0; i < mbuf->mb_count; i++) { | |
1688 | if (MB_TYPE(mbuf, i) & MODE_ADD) { /* adding or removing? */ | |
1689 | bufptr = addbuf; | |
1690 | bufptr_i = &addbuf_i; | |
1691 | } else { | |
1692 | bufptr = rembuf; | |
1693 | bufptr_i = &rembuf_i; | |
1694 | } | |
1695 | ||
1696 | if (MB_TYPE(mbuf, i) & (MODE_CHANOP | MODE_VOICE)) { | |
1697 | tmp = strlen(cli_name(MB_CLIENT(mbuf, i))); | |
1698 | ||
1699 | if ((totalbuflen - IRCD_MAX(9, tmp)) <= 0) /* don't overflow buffer */ | |
1700 | MB_TYPE(mbuf, i) |= MODE_SAVE; /* save for later */ | |
1701 | else { | |
1702 | bufptr[(*bufptr_i)++] = MB_TYPE(mbuf, i) & MODE_CHANOP ? 'o' : 'v'; | |
1703 | totalbuflen -= IRCD_MAX(9, tmp) + 1; | |
1704 | } | |
1705 | } else if (MB_TYPE(mbuf, i) & (MODE_BAN | MODE_APASS | MODE_UPASS)) { | |
1706 | tmp = strlen(MB_STRING(mbuf, i)); | |
1707 | ||
1708 | if ((totalbuflen - tmp) <= 0) /* don't overflow buffer */ | |
1709 | MB_TYPE(mbuf, i) |= MODE_SAVE; /* save for later */ | |
1710 | else { | |
1711 | char mode_char; | |
1712 | switch(MB_TYPE(mbuf, i) & (MODE_BAN | MODE_APASS | MODE_UPASS)) | |
1713 | { | |
1714 | case MODE_APASS: | |
1715 | mode_char = 'A'; | |
1716 | break; | |
1717 | case MODE_UPASS: | |
1718 | mode_char = 'U'; | |
1719 | break; | |
1720 | default: | |
1721 | mode_char = 'b'; | |
1722 | break; | |
1723 | } | |
1724 | bufptr[(*bufptr_i)++] = mode_char; | |
1725 | totalbuflen -= tmp + 1; | |
1726 | } | |
1727 | } else if (MB_TYPE(mbuf, i) & MODE_KEY) { | |
1728 | tmp = (mbuf->mb_dest & MODEBUF_DEST_NOKEY ? 1 : | |
1729 | strlen(MB_STRING(mbuf, i))); | |
1730 | ||
1731 | if ((totalbuflen - tmp) <= 0) /* don't overflow buffer */ | |
1732 | MB_TYPE(mbuf, i) |= MODE_SAVE; /* save for later */ | |
1733 | else { | |
1734 | bufptr[(*bufptr_i)++] = 'k'; | |
1735 | totalbuflen -= tmp + 1; | |
1736 | } | |
1737 | } else if (MB_TYPE(mbuf, i) & MODE_LIMIT) { | |
1738 | /* if it's a limit, we also format the number */ | |
1739 | ircd_snprintf(0, limitbuf, sizeof(limitbuf), "%u", MB_UINT(mbuf, i)); | |
1740 | ||
1741 | tmp = strlen(limitbuf); | |
1742 | ||
1743 | if ((totalbuflen - tmp) <= 0) /* don't overflow buffer */ | |
1744 | MB_TYPE(mbuf, i) |= MODE_SAVE; /* save for later */ | |
1745 | else { | |
1746 | bufptr[(*bufptr_i)++] = 'l'; | |
1747 | totalbuflen -= tmp + 1; | |
1748 | } | |
1749 | } | |
1750 | } | |
1751 | ||
1752 | /* terminate the mode strings */ | |
1753 | addbuf[addbuf_i] = '\0'; | |
1754 | rembuf[rembuf_i] = '\0'; | |
052b069e | 1755 | addbuf_local[addbuf_local_i] = '\0'; |
1756 | rembuf_local[rembuf_local_i] = '\0'; | |
189935b1 | 1757 | |
1758 | /* If we're building a user visible MODE or HACK... */ | |
1759 | if (mbuf->mb_dest & (MODEBUF_DEST_CHANNEL | MODEBUF_DEST_HACK2 | | |
1760 | MODEBUF_DEST_HACK3 | MODEBUF_DEST_HACK4 | | |
1761 | MODEBUF_DEST_LOG)) { | |
1762 | /* Set up the parameter strings */ | |
1763 | addstr[0] = '\0'; | |
1764 | addstr_i = 0; | |
1765 | remstr[0] = '\0'; | |
1766 | remstr_i = 0; | |
1767 | ||
1768 | for (i = 0; i < mbuf->mb_count; i++) { | |
1769 | if (MB_TYPE(mbuf, i) & MODE_SAVE) | |
1770 | continue; | |
1771 | ||
1772 | if (MB_TYPE(mbuf, i) & MODE_ADD) { /* adding or removing? */ | |
1773 | strptr = addstr; | |
1774 | strptr_i = &addstr_i; | |
1775 | } else { | |
1776 | strptr = remstr; | |
1777 | strptr_i = &remstr_i; | |
1778 | } | |
1779 | ||
1780 | /* deal with clients... */ | |
1781 | if (MB_TYPE(mbuf, i) & (MODE_CHANOP | MODE_VOICE)) | |
1782 | build_string(strptr, strptr_i, cli_name(MB_CLIENT(mbuf, i)), 0, ' '); | |
1783 | ||
1784 | /* deal with bans... */ | |
1785 | else if (MB_TYPE(mbuf, i) & MODE_BAN) | |
1786 | build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' '); | |
1787 | ||
1788 | /* deal with keys... */ | |
1789 | else if (MB_TYPE(mbuf, i) & MODE_KEY) | |
1790 | build_string(strptr, strptr_i, mbuf->mb_dest & MODEBUF_DEST_NOKEY ? | |
1791 | "*" : MB_STRING(mbuf, i), 0, ' '); | |
1792 | ||
1793 | /* deal with invisible passwords */ | |
1794 | else if (MB_TYPE(mbuf, i) & (MODE_APASS | MODE_UPASS)) | |
1795 | build_string(strptr, strptr_i, "*", 0, ' '); | |
1796 | ||
1797 | /* | |
1798 | * deal with limit; note we cannot include the limit parameter if we're | |
1799 | * removing it | |
1800 | */ | |
1801 | else if ((MB_TYPE(mbuf, i) & (MODE_ADD | MODE_LIMIT)) == | |
1802 | (MODE_ADD | MODE_LIMIT)) | |
1803 | build_string(strptr, strptr_i, limitbuf, 0, ' '); | |
1804 | } | |
1805 | ||
1806 | /* send the messages off to their destination */ | |
1807 | if (mbuf->mb_dest & MODEBUF_DEST_HACK2) | |
1808 | sendto_opmask_butone(0, SNO_HACK2, "HACK(2): %s MODE %s %s%s%s%s%s%s " | |
1809 | "[%Tu]", | |
1810 | cli_name(feature_bool(FEAT_HIS_SNOTICES) ? | |
1811 | mbuf->mb_source : app_source), | |
1812 | mbuf->mb_channel->chname, | |
1813 | rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "", | |
1814 | addbuf, remstr, addstr, | |
1815 | mbuf->mb_channel->creationtime); | |
1816 | ||
1817 | if (mbuf->mb_dest & MODEBUF_DEST_HACK3) | |
1818 | sendto_opmask_butone(0, SNO_HACK3, "BOUNCE or HACK(3): %s MODE %s " | |
1819 | "%s%s%s%s%s%s [%Tu]", | |
1820 | cli_name(feature_bool(FEAT_HIS_SNOTICES) ? | |
1821 | mbuf->mb_source : app_source), | |
1822 | mbuf->mb_channel->chname, rembuf_i ? "-" : "", | |
1823 | rembuf, addbuf_i ? "+" : "", addbuf, remstr, addstr, | |
1824 | mbuf->mb_channel->creationtime); | |
1825 | ||
1826 | if (mbuf->mb_dest & MODEBUF_DEST_HACK4) | |
1827 | sendto_opmask_butone(0, SNO_HACK4, "HACK(4): %s MODE %s %s%s%s%s%s%s " | |
1828 | "[%Tu]", | |
1829 | cli_name(feature_bool(FEAT_HIS_SNOTICES) ? | |
1830 | mbuf->mb_source : app_source), | |
1831 | mbuf->mb_channel->chname, | |
1832 | rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "", | |
1833 | addbuf, remstr, addstr, | |
1834 | mbuf->mb_channel->creationtime); | |
1835 | ||
1836 | if (mbuf->mb_dest & MODEBUF_DEST_LOG) | |
1837 | log_write(LS_OPERMODE, L_INFO, LOG_NOSNOTICE, | |
1838 | "%#C OPMODE %H %s%s%s%s%s%s", mbuf->mb_source, | |
1839 | mbuf->mb_channel, rembuf_i ? "-" : "", rembuf, | |
1840 | addbuf_i ? "+" : "", addbuf, remstr, addstr); | |
1841 | ||
1842 | if (mbuf->mb_dest & MODEBUF_DEST_CHANNEL) | |
1843 | sendcmdto_channel_butserv_butone(app_source, CMD_MODE, mbuf->mb_channel, NULL, 0, | |
052b069e | 1844 | "%H %s%s%s%s%s%s%s%s", mbuf->mb_channel, |
1845 | rembuf_i || rembuf_local_i ? "-" : "", | |
1846 | rembuf, rembuf_local, | |
1847 | addbuf_i || addbuf_local_i ? "+" : "", | |
1848 | addbuf, addbuf_local, | |
1849 | remstr, addstr); | |
189935b1 | 1850 | } |
1851 | ||
1852 | /* Now are we supposed to propagate to other servers? */ | |
1853 | if (mbuf->mb_dest & MODEBUF_DEST_SERVER) { | |
1854 | /* set up parameter string */ | |
1855 | addstr[0] = '\0'; | |
1856 | addstr_i = 0; | |
1857 | remstr[0] = '\0'; | |
1858 | remstr_i = 0; | |
1859 | ||
1860 | /* | |
1861 | * limit is supressed if we're removing it; we have to figure out which | |
1862 | * direction is the direction for it to be removed, though... | |
1863 | */ | |
052b069e | 1864 | limitdel |= (mbuf->mb_dest & MODEBUF_DEST_BOUNCE) ? MODE_DEL : MODE_ADD; |
189935b1 | 1865 | |
1866 | for (i = 0; i < mbuf->mb_count; i++) { | |
1867 | if (MB_TYPE(mbuf, i) & MODE_SAVE) | |
1868 | continue; | |
1869 | ||
1870 | if (MB_TYPE(mbuf, i) & MODE_ADD) { /* adding or removing? */ | |
1871 | strptr = addstr; | |
1872 | strptr_i = &addstr_i; | |
1873 | } else { | |
1874 | strptr = remstr; | |
1875 | strptr_i = &remstr_i; | |
1876 | } | |
1877 | ||
9f8856e9 | 1878 | /* if we're changing oplevels and we know the oplevel, pass it on */ |
0c466275 | 1879 | if ((MB_TYPE(mbuf, i) & MODE_CHANOP) |
189935b1 | 1880 | && MB_OPLEVEL(mbuf, i) < MAXOPLEVEL) |
1881 | *strptr_i += ircd_snprintf(0, strptr + *strptr_i, BUFSIZE - *strptr_i, | |
1882 | " %s%s:%d", | |
1883 | NumNick(MB_CLIENT(mbuf, i)), | |
1884 | MB_OPLEVEL(mbuf, i)); | |
1885 | ||
1886 | /* deal with other modes that take clients */ | |
1887 | else if (MB_TYPE(mbuf, i) & (MODE_CHANOP | MODE_VOICE)) | |
1888 | build_string(strptr, strptr_i, NumNick(MB_CLIENT(mbuf, i)), ' '); | |
1889 | ||
1890 | /* deal with modes that take strings */ | |
1891 | else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN | MODE_APASS | MODE_UPASS)) | |
1892 | build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' '); | |
1893 | ||
1894 | /* | |
1895 | * deal with the limit. Logic here is complicated; if HACK2 is set, | |
1896 | * we're bouncing the mode, so sense is reversed, and we have to | |
1897 | * include the original limit if it looks like it's being removed | |
1898 | */ | |
1899 | else if ((MB_TYPE(mbuf, i) & limitdel) == limitdel) | |
1900 | build_string(strptr, strptr_i, limitbuf, 0, ' '); | |
1901 | } | |
1902 | ||
1903 | /* we were told to deop the source */ | |
1904 | if (mbuf->mb_dest & MODEBUF_DEST_DEOP) { | |
1905 | addbuf[addbuf_i++] = 'o'; /* remember, sense is reversed */ | |
1906 | addbuf[addbuf_i] = '\0'; /* terminate the string... */ | |
1907 | build_string(addstr, &addstr_i, NumNick(mbuf->mb_source), ' '); | |
1908 | ||
1909 | /* mark that we've done this, so we don't do it again */ | |
1910 | mbuf->mb_dest &= ~MODEBUF_DEST_DEOP; | |
1911 | } | |
1912 | ||
1913 | if (mbuf->mb_dest & MODEBUF_DEST_OPMODE) { | |
1914 | /* If OPMODE was set, we're propagating the mode as an OPMODE message */ | |
1915 | sendcmdto_serv_butone(mbuf->mb_source, CMD_OPMODE, mbuf->mb_connect, | |
1916 | "%H %s%s%s%s%s%s", mbuf->mb_channel, | |
1917 | rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "", | |
1918 | addbuf, remstr, addstr); | |
1919 | } else if (mbuf->mb_dest & MODEBUF_DEST_BOUNCE) { | |
1920 | /* | |
9f8856e9 | 1921 | * If HACK2 was set, we're bouncing; we send the MODE back to |
1922 | * the connection we got it from with the senses reversed and | |
1923 | * the proper TS; origin is us | |
189935b1 | 1924 | */ |
1925 | sendcmdto_one(&me, CMD_MODE, mbuf->mb_connect, "%H %s%s%s%s%s%s %Tu", | |
1926 | mbuf->mb_channel, addbuf_i ? "-" : "", addbuf, | |
1927 | rembuf_i ? "+" : "", rembuf, addstr, remstr, | |
1928 | mbuf->mb_channel->creationtime); | |
1929 | } else { | |
1930 | /* | |
9f8856e9 | 1931 | * We're propagating a normal (or HACK3 or HACK4) MODE command |
1932 | * to the rest of the network. We send the actual channel TS. | |
189935b1 | 1933 | */ |
9f8856e9 | 1934 | sendcmdto_serv_butone(mbuf->mb_source, CMD_MODE, mbuf->mb_connect, |
1935 | "%H %s%s%s%s%s%s %Tu", mbuf->mb_channel, | |
1936 | rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "", | |
1937 | addbuf, remstr, addstr, | |
1938 | mbuf->mb_channel->creationtime); | |
189935b1 | 1939 | } |
1940 | } | |
1941 | ||
1942 | /* We've drained the ModeBuf... */ | |
1943 | mbuf->mb_add = 0; | |
1944 | mbuf->mb_rem = 0; | |
1945 | mbuf->mb_count = 0; | |
1946 | ||
1947 | /* reinitialize the mode-with-arg slots */ | |
1948 | for (i = 0; i < MAXMODEPARAMS; i++) { | |
1949 | /* If we saved any, pack them down */ | |
1950 | if (MB_TYPE(mbuf, i) & MODE_SAVE) { | |
1951 | mbuf->mb_modeargs[mbuf->mb_count] = mbuf->mb_modeargs[i]; | |
1952 | MB_TYPE(mbuf, mbuf->mb_count) &= ~MODE_SAVE; /* don't save anymore */ | |
1953 | ||
1954 | if (mbuf->mb_count++ == i) /* don't overwrite our hard work */ | |
1955 | continue; | |
1956 | } else if (MB_TYPE(mbuf, i) & MODE_FREE) | |
1957 | MyFree(MB_STRING(mbuf, i)); /* free string if needed */ | |
1958 | ||
1959 | MB_TYPE(mbuf, i) = 0; | |
1960 | MB_UINT(mbuf, i) = 0; | |
1961 | } | |
1962 | ||
1963 | /* If we're supposed to flush it all, do so--all hail tail recursion */ | |
1964 | if (all && mbuf->mb_count) | |
1965 | return modebuf_flush_int(mbuf, 1); | |
1966 | ||
1967 | return 0; | |
1968 | } | |
1969 | ||
1970 | /** Initialise a modebuf | |
1971 | * This routine just initializes a ModeBuf structure with the information | |
1972 | * needed and the options given. | |
1973 | * | |
1974 | * @param mbuf The mode buffer to initialise. | |
1975 | * @param source The client that is performing the mode. | |
1976 | * @param connect ? | |
1977 | * @param chan The channel that the mode is being performed upon. | |
1978 | * @param dest ? | |
1979 | */ | |
1980 | void | |
1981 | modebuf_init(struct ModeBuf *mbuf, struct Client *source, | |
1982 | struct Client *connect, struct Channel *chan, unsigned int dest) | |
1983 | { | |
1984 | int i; | |
1985 | ||
1986 | assert(0 != mbuf); | |
1987 | assert(0 != source); | |
1988 | assert(0 != chan); | |
1989 | assert(0 != dest); | |
1990 | ||
1991 | if (IsLocalChannel(chan->chname)) dest &= ~MODEBUF_DEST_SERVER; | |
1992 | ||
1993 | mbuf->mb_add = 0; | |
1994 | mbuf->mb_rem = 0; | |
1995 | mbuf->mb_source = source; | |
1996 | mbuf->mb_connect = connect; | |
1997 | mbuf->mb_channel = chan; | |
1998 | mbuf->mb_dest = dest; | |
1999 | mbuf->mb_count = 0; | |
2000 | ||
2001 | /* clear each mode-with-parameter slot */ | |
2002 | for (i = 0; i < MAXMODEPARAMS; i++) { | |
2003 | MB_TYPE(mbuf, i) = 0; | |
2004 | MB_UINT(mbuf, i) = 0; | |
2005 | } | |
2006 | } | |
2007 | ||
2008 | /** Append a new mode to a modebuf | |
2009 | * This routine simply adds modes to be added or deleted; do a binary OR | |
2010 | * with either MODE_ADD or MODE_DEL | |
2011 | * | |
2012 | * @param mbuf Mode buffer | |
2013 | * @param mode MODE_ADD or MODE_DEL OR'd with MODE_PRIVATE etc. | |
2014 | */ | |
2015 | void | |
2016 | modebuf_mode(struct ModeBuf *mbuf, unsigned int mode) | |
2017 | { | |
2018 | assert(0 != mbuf); | |
2019 | assert(0 != (mode & (MODE_ADD | MODE_DEL))); | |
2020 | ||
2021 | mode &= (MODE_ADD | MODE_DEL | MODE_PRIVATE | MODE_SECRET | MODE_MODERATED | | |
d8e74551 | 2022 | MODE_TOPICLIMIT | MODE_INVITEONLY | MODE_NOPRIVMSGS | MODE_REGONLY | |
2023 | MODE_DELJOINS | MODE_WASDELJOINS | MODE_NOQUITPARTS | MODE_NOCOLOUR | | |
1cade670 | 2024 | MODE_NOCTCP | MODE_NONOTICE | MODE_NOMULTITARGET | MODE_MODERATENOREG); |
189935b1 | 2025 | |
2026 | if (!(mode & ~(MODE_ADD | MODE_DEL))) /* don't add empty modes... */ | |
2027 | return; | |
2028 | ||
2029 | if (mode & MODE_ADD) { | |
2030 | mbuf->mb_rem &= ~mode; | |
2031 | mbuf->mb_add |= mode; | |
2032 | } else { | |
2033 | mbuf->mb_add &= ~mode; | |
2034 | mbuf->mb_rem |= mode; | |
2035 | } | |
2036 | } | |
2037 | ||
2038 | /** Append a mode that takes an int argument to the modebuf | |
2039 | * | |
2040 | * This routine adds a mode to be added or deleted that takes a unsigned | |
2041 | * int parameter; mode may *only* be the relevant mode flag ORed with one | |
2042 | * of MODE_ADD or MODE_DEL | |
2043 | * | |
2044 | * @param mbuf The mode buffer to append to. | |
2045 | * @param mode The mode to append. | |
2046 | * @param uint The argument to the mode. | |
2047 | */ | |
2048 | void | |
2049 | modebuf_mode_uint(struct ModeBuf *mbuf, unsigned int mode, unsigned int uint) | |
2050 | { | |
2051 | assert(0 != mbuf); | |
2052 | assert(0 != (mode & (MODE_ADD | MODE_DEL))); | |
2053 | ||
052b069e | 2054 | if (mode == (MODE_LIMIT | ((mbuf->mb_dest & MODEBUF_DEST_BOUNCE) ? MODE_ADD : MODE_DEL))) { |
189935b1 | 2055 | mbuf->mb_rem |= mode; |
2056 | return; | |
2057 | } | |
2058 | MB_TYPE(mbuf, mbuf->mb_count) = mode; | |
2059 | MB_UINT(mbuf, mbuf->mb_count) = uint; | |
2060 | ||
2061 | /* when we've reached the maximal count, flush the buffer */ | |
2062 | if (++mbuf->mb_count >= | |
2063 | (MAXMODEPARAMS - (mbuf->mb_dest & MODEBUF_DEST_DEOP ? 1 : 0))) | |
2064 | modebuf_flush_int(mbuf, 0); | |
2065 | } | |
2066 | ||
2067 | /** append a string mode | |
2068 | * This routine adds a mode to be added or deleted that takes a string | |
2069 | * parameter; mode may *only* be the relevant mode flag ORed with one of | |
2070 | * MODE_ADD or MODE_DEL | |
2071 | * | |
2072 | * @param mbuf The mode buffer to append to. | |
2073 | * @param mode The mode to append. | |
2074 | * @param string The string parameter to append. | |
2075 | * @param free If the string should be free'd later. | |
2076 | */ | |
2077 | void | |
2078 | modebuf_mode_string(struct ModeBuf *mbuf, unsigned int mode, char *string, | |
2079 | int free) | |
2080 | { | |
2081 | assert(0 != mbuf); | |
2082 | assert(0 != (mode & (MODE_ADD | MODE_DEL))); | |
2083 | ||
2084 | MB_TYPE(mbuf, mbuf->mb_count) = mode | (free ? MODE_FREE : 0); | |
2085 | MB_STRING(mbuf, mbuf->mb_count) = string; | |
2086 | ||
2087 | /* when we've reached the maximal count, flush the buffer */ | |
2088 | if (++mbuf->mb_count >= | |
2089 | (MAXMODEPARAMS - (mbuf->mb_dest & MODEBUF_DEST_DEOP ? 1 : 0))) | |
2090 | modebuf_flush_int(mbuf, 0); | |
2091 | } | |
2092 | ||
2093 | /** Append a mode on a client to a modebuf. | |
2094 | * This routine adds a mode to be added or deleted that takes a client | |
2095 | * parameter; mode may *only* be the relevant mode flag ORed with one of | |
2096 | * MODE_ADD or MODE_DEL | |
2097 | * | |
2098 | * @param mbuf The modebuf to append the mode to. | |
2099 | * @param mode The mode to append. | |
2100 | * @param client The client argument to append. | |
2101 | * @param oplevel The oplevel the user had or will have | |
2102 | */ | |
2103 | void | |
2104 | modebuf_mode_client(struct ModeBuf *mbuf, unsigned int mode, | |
2105 | struct Client *client, int oplevel) | |
2106 | { | |
2107 | assert(0 != mbuf); | |
2108 | assert(0 != (mode & (MODE_ADD | MODE_DEL))); | |
2109 | ||
2110 | MB_TYPE(mbuf, mbuf->mb_count) = mode; | |
2111 | MB_CLIENT(mbuf, mbuf->mb_count) = client; | |
2112 | MB_OPLEVEL(mbuf, mbuf->mb_count) = oplevel; | |
2113 | ||
2114 | /* when we've reached the maximal count, flush the buffer */ | |
2115 | if (++mbuf->mb_count >= | |
2116 | (MAXMODEPARAMS - (mbuf->mb_dest & MODEBUF_DEST_DEOP ? 1 : 0))) | |
2117 | modebuf_flush_int(mbuf, 0); | |
2118 | } | |
2119 | ||
4d6475c6 | 2120 | /** Check a channel for join-delayed members. |
2121 | * @param[in] chan Channel to search. | |
2122 | * @return Non-zero if any members are join-delayed; false if none are. | |
2123 | */ | |
2124 | static int | |
2125 | find_delayed_joins(const struct Channel *chan) | |
2126 | { | |
2127 | const struct Membership *memb; | |
2128 | for (memb = chan->members; memb; memb = memb->next_member) | |
2129 | if (IsDelayedJoin(memb)) | |
2130 | return 1; | |
2131 | return 0; | |
2132 | } | |
2133 | ||
189935b1 | 2134 | /** The exported binding for modebuf_flush() |
2135 | * | |
2136 | * @param mbuf The mode buffer to flush. | |
2137 | * | |
2138 | * @see modebuf_flush_int() | |
2139 | */ | |
2140 | int | |
2141 | modebuf_flush(struct ModeBuf *mbuf) | |
2142 | { | |
4d6475c6 | 2143 | /* Check if MODE_WASDELJOINS should be set: */ |
2144 | /* Must be set if going -D and some clients are hidden */ | |
2145 | if ((mbuf->mb_rem & MODE_DELJOINS) | |
2146 | && !(mbuf->mb_channel->mode.mode & (MODE_DELJOINS | MODE_WASDELJOINS)) | |
2147 | && find_delayed_joins(mbuf->mb_channel)) { | |
2148 | mbuf->mb_channel->mode.mode |= MODE_WASDELJOINS; | |
2149 | mbuf->mb_add |= MODE_WASDELJOINS; | |
2150 | mbuf->mb_rem &= ~MODE_WASDELJOINS; | |
2151 | } | |
2152 | /* Must be cleared if +D is set */ | |
2153 | if ((mbuf->mb_add & MODE_DELJOINS) | |
2154 | && ((mbuf->mb_channel->mode.mode & (MODE_WASDELJOINS | MODE_WASDELJOINS)) | |
2155 | == (MODE_WASDELJOINS | MODE_WASDELJOINS))) { | |
2156 | mbuf->mb_channel->mode.mode &= ~MODE_WASDELJOINS; | |
2157 | mbuf->mb_add &= ~MODE_WASDELJOINS; | |
2158 | mbuf->mb_rem |= MODE_WASDELJOINS; | |
189935b1 | 2159 | } |
2160 | ||
2161 | return modebuf_flush_int(mbuf, 1); | |
2162 | } | |
2163 | ||
2164 | /* This extracts the simple modes contained in mbuf | |
2165 | * | |
2166 | * @param mbuf The mode buffer to extract the modes from. | |
2167 | * @param buf The string buffer to write the modes into. | |
2168 | */ | |
2169 | void | |
2170 | modebuf_extract(struct ModeBuf *mbuf, char *buf) | |
2171 | { | |
2172 | static int flags[] = { | |
2173 | /* MODE_CHANOP, 'o', */ | |
2174 | /* MODE_VOICE, 'v', */ | |
2175 | MODE_PRIVATE, 'p', | |
2176 | MODE_SECRET, 's', | |
2177 | MODE_MODERATED, 'm', | |
2178 | MODE_TOPICLIMIT, 't', | |
2179 | MODE_INVITEONLY, 'i', | |
2180 | MODE_NOPRIVMSGS, 'n', | |
2181 | MODE_KEY, 'k', | |
2182 | MODE_APASS, 'A', | |
2183 | MODE_UPASS, 'U', | |
2184 | /* MODE_BAN, 'b', */ | |
2185 | MODE_LIMIT, 'l', | |
2186 | MODE_REGONLY, 'r', | |
2187 | MODE_DELJOINS, 'D', | |
d8e74551 | 2188 | MODE_NOQUITPARTS, 'u', |
2189 | MODE_NOCOLOUR, 'c', | |
2190 | MODE_NOCTCP, 'C', | |
2191 | MODE_NONOTICE, 'N', | |
1cade670 | 2192 | MODE_NOMULTITARGET, 'T', |
2193 | MODE_MODERATENOREG, 'M', | |
189935b1 | 2194 | 0x0, 0x0 |
2195 | }; | |
2196 | unsigned int add; | |
2197 | int i, bufpos = 0, len; | |
2198 | int *flag_p; | |
2199 | char *key = 0, limitbuf[20]; | |
2200 | char *apass = 0, *upass = 0; | |
2201 | ||
2202 | assert(0 != mbuf); | |
2203 | assert(0 != buf); | |
2204 | ||
2205 | buf[0] = '\0'; | |
2206 | ||
2207 | add = mbuf->mb_add; | |
2208 | ||
2209 | for (i = 0; i < mbuf->mb_count; i++) { /* find keys and limits */ | |
2210 | if (MB_TYPE(mbuf, i) & MODE_ADD) { | |
2211 | add |= MB_TYPE(mbuf, i) & (MODE_KEY | MODE_LIMIT | MODE_APASS | MODE_UPASS); | |
2212 | ||
2213 | if (MB_TYPE(mbuf, i) & MODE_KEY) /* keep strings */ | |
2214 | key = MB_STRING(mbuf, i); | |
2215 | else if (MB_TYPE(mbuf, i) & MODE_LIMIT) | |
2216 | ircd_snprintf(0, limitbuf, sizeof(limitbuf), "%u", MB_UINT(mbuf, i)); | |
2217 | else if (MB_TYPE(mbuf, i) & MODE_UPASS) | |
2218 | upass = MB_STRING(mbuf, i); | |
2219 | else if (MB_TYPE(mbuf, i) & MODE_APASS) | |
2220 | apass = MB_STRING(mbuf, i); | |
2221 | } | |
2222 | } | |
2223 | ||
2224 | if (!add) | |
2225 | return; | |
2226 | ||
2227 | buf[bufpos++] = '+'; /* start building buffer */ | |
2228 | ||
2229 | for (flag_p = flags; flag_p[0]; flag_p += 2) | |
2230 | if (*flag_p & add) | |
2231 | buf[bufpos++] = flag_p[1]; | |
2232 | ||
2233 | for (i = 0, len = bufpos; i < len; i++) { | |
2234 | if (buf[i] == 'k') | |
2235 | build_string(buf, &bufpos, key, 0, ' '); | |
2236 | else if (buf[i] == 'l') | |
2237 | build_string(buf, &bufpos, limitbuf, 0, ' '); | |
2238 | else if (buf[i] == 'U') | |
2239 | build_string(buf, &bufpos, upass, 0, ' '); | |
2240 | else if (buf[i] == 'A') | |
2241 | build_string(buf, &bufpos, apass, 0, ' '); | |
2242 | } | |
2243 | ||
2244 | buf[bufpos] = '\0'; | |
2245 | ||
2246 | return; | |
2247 | } | |
2248 | ||
a351d4e2 | 2249 | /** Simple function to invalidate a channel's ban cache. |
189935b1 | 2250 | * |
a351d4e2 | 2251 | * This function marks all members of the channel as being neither |
2252 | * banned nor banned. | |
189935b1 | 2253 | * |
2254 | * @param chan The channel to operate on. | |
2255 | */ | |
2256 | void | |
2257 | mode_ban_invalidate(struct Channel *chan) | |
2258 | { | |
2259 | struct Membership *member; | |
2260 | ||
2261 | for (member = chan->members; member; member = member->next_member) | |
2262 | ClearBanValid(member); | |
2263 | } | |
2264 | ||
2265 | /** Simple function to drop invite structures | |
2266 | * | |
2267 | * Remove all the invites on the channel. | |
2268 | * | |
2269 | * @param chan Channel to remove invites from. | |
2270 | * | |
2271 | */ | |
2272 | void | |
2273 | mode_invite_clear(struct Channel *chan) | |
2274 | { | |
2275 | while (chan->invites) | |
2276 | del_invite(chan->invites->value.cptr, chan); | |
2277 | } | |
2278 | ||
2279 | /* What we've done for mode_parse so far... */ | |
2280 | #define DONE_LIMIT 0x01 /**< We've set the limit */ | |
9fe6dff2 | 2281 | #define DONE_KEY_ADD 0x02 /**< We've set the key */ |
189935b1 | 2282 | #define DONE_BANLIST 0x04 /**< We've sent the ban list */ |
2283 | #define DONE_NOTOPER 0x08 /**< We've sent a "Not oper" error */ | |
2284 | #define DONE_BANCLEAN 0x10 /**< We've cleaned bans... */ | |
9fe6dff2 | 2285 | #define DONE_UPASS_ADD 0x20 /**< We've set user pass */ |
2286 | #define DONE_APASS_ADD 0x40 /**< We've set admin pass */ | |
2287 | #define DONE_KEY_DEL 0x80 /**< We've removed the key */ | |
2288 | #define DONE_UPASS_DEL 0x100 /**< We've removed the user pass */ | |
2289 | #define DONE_APASS_DEL 0x200 /**< We've removed the admin pass */ | |
189935b1 | 2290 | |
2291 | struct ParseState { | |
2292 | struct ModeBuf *mbuf; | |
2293 | struct Client *cptr; | |
2294 | struct Client *sptr; | |
2295 | struct Channel *chptr; | |
2296 | struct Membership *member; | |
2297 | int parc; | |
2298 | char **parv; | |
2299 | unsigned int flags; | |
2300 | unsigned int dir; | |
2301 | unsigned int done; | |
2302 | unsigned int add; | |
2303 | unsigned int del; | |
2304 | int args_used; | |
2305 | int max_args; | |
2306 | int numbans; | |
2307 | struct Ban banlist[MAXPARA]; | |
2308 | struct { | |
2309 | unsigned int flag; | |
2310 | unsigned short oplevel; | |
2311 | struct Client *client; | |
2312 | } cli_change[MAXPARA]; | |
2313 | }; | |
2314 | ||
2315 | /** Helper function to send "Not oper" or "Not member" messages | |
2316 | * Here's a helper function to deal with sending along "Not oper" or | |
2317 | * "Not member" messages | |
2318 | * | |
2319 | * @param state Parsing State object | |
2320 | */ | |
2321 | static void | |
2322 | send_notoper(struct ParseState *state) | |
2323 | { | |
2324 | if (state->done & DONE_NOTOPER) | |
2325 | return; | |
2326 | ||
2327 | send_reply(state->sptr, (state->flags & MODE_PARSE_NOTOPER) ? | |
2328 | ERR_CHANOPRIVSNEEDED : ERR_NOTONCHANNEL, state->chptr->chname); | |
2329 | ||
2330 | state->done |= DONE_NOTOPER; | |
2331 | } | |
2332 | ||
2333 | /** Parse a limit | |
2334 | * Helper function to convert limits | |
2335 | * | |
2336 | * @param state Parsing state object. | |
2337 | * @param flag_p ? | |
2338 | */ | |
2339 | static void | |
2340 | mode_parse_limit(struct ParseState *state, int *flag_p) | |
2341 | { | |
2342 | unsigned int t_limit; | |
2343 | ||
2344 | if (state->dir == MODE_ADD) { /* convert arg only if adding limit */ | |
2345 | if (MyUser(state->sptr) && state->max_args <= 0) /* too many args? */ | |
2346 | return; | |
2347 | ||
2348 | if (state->parc <= 0) { /* warn if not enough args */ | |
2349 | if (MyUser(state->sptr)) | |
2350 | need_more_params(state->sptr, "MODE +l"); | |
2351 | return; | |
2352 | } | |
2353 | ||
2354 | t_limit = strtoul(state->parv[state->args_used++], 0, 10); /* grab arg */ | |
2355 | state->parc--; | |
2356 | state->max_args--; | |
2357 | ||
2358 | if ((int)t_limit<0) /* don't permit a negative limit */ | |
2359 | return; | |
2360 | ||
2361 | if (!(state->flags & MODE_PARSE_WIPEOUT) && | |
2362 | (!t_limit || t_limit == state->chptr->mode.limit)) | |
2363 | return; | |
2364 | } else | |
2365 | t_limit = state->chptr->mode.limit; | |
2366 | ||
2367 | /* If they're not an oper, they can't change modes */ | |
2368 | if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) { | |
2369 | send_notoper(state); | |
2370 | return; | |
2371 | } | |
2372 | ||
2373 | /* Can't remove a limit that's not there */ | |
2374 | if (state->dir == MODE_DEL && !state->chptr->mode.limit) | |
2375 | return; | |
2376 | ||
2377 | /* Skip if this is a burst and a lower limit than this is set already */ | |
2378 | if ((state->flags & MODE_PARSE_BURST) && | |
2379 | (state->chptr->mode.mode & flag_p[0]) && | |
2380 | (state->chptr->mode.limit < t_limit)) | |
2381 | return; | |
2382 | ||
2383 | if (state->done & DONE_LIMIT) /* allow limit to be set only once */ | |
2384 | return; | |
2385 | state->done |= DONE_LIMIT; | |
2386 | ||
2387 | if (!state->mbuf) | |
2388 | return; | |
2389 | ||
2390 | modebuf_mode_uint(state->mbuf, state->dir | flag_p[0], t_limit); | |
2391 | ||
2392 | if (state->flags & MODE_PARSE_SET) { /* set the limit */ | |
2393 | if (state->dir & MODE_ADD) { | |
2394 | state->chptr->mode.mode |= flag_p[0]; | |
2395 | state->chptr->mode.limit = t_limit; | |
2396 | } else { | |
2397 | state->chptr->mode.mode &= ~flag_p[0]; | |
2398 | state->chptr->mode.limit = 0; | |
2399 | } | |
2400 | } | |
2401 | } | |
2402 | ||
2403 | /** Helper function to clean key-like parameters. */ | |
2404 | static void | |
2405 | clean_key(char *s) | |
2406 | { | |
2407 | int t_len = KEYLEN; | |
2408 | ||
2409 | while (*s > ' ' && *s != ':' && *s != ',' && t_len--) | |
2410 | s++; | |
2411 | *s = '\0'; | |
2412 | } | |
2413 | ||
2414 | /* | |
2415 | * Helper function to convert keys | |
2416 | */ | |
2417 | static void | |
2418 | mode_parse_key(struct ParseState *state, int *flag_p) | |
2419 | { | |
2420 | char *t_str; | |
2421 | ||
2422 | if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */ | |
2423 | return; | |
2424 | ||
2425 | if (state->parc <= 0) { /* warn if not enough args */ | |
2426 | if (MyUser(state->sptr)) | |
2427 | need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +k" : | |
2428 | "MODE -k"); | |
2429 | return; | |
2430 | } | |
2431 | ||
2432 | t_str = state->parv[state->args_used++]; /* grab arg */ | |
2433 | state->parc--; | |
2434 | state->max_args--; | |
2435 | ||
2436 | /* If they're not an oper, they can't change modes */ | |
2437 | if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) { | |
2438 | send_notoper(state); | |
2439 | return; | |
2440 | } | |
2441 | ||
9fe6dff2 | 2442 | /* allow removing and then adding key, but not adding and then removing */ |
2443 | if (state->dir == MODE_ADD) | |
2444 | { | |
2445 | if (state->done & DONE_KEY_ADD) | |
2446 | return; | |
2447 | state->done |= DONE_KEY_ADD; | |
2448 | } | |
2449 | else | |
2450 | { | |
2451 | if (state->done & (DONE_KEY_ADD | DONE_KEY_DEL)) | |
2452 | return; | |
2453 | state->done |= DONE_KEY_DEL; | |
2454 | } | |
189935b1 | 2455 | |
2456 | /* clean up the key string */ | |
2457 | clean_key(t_str); | |
2458 | if (!*t_str || *t_str == ':') { /* warn if empty */ | |
2459 | if (MyUser(state->sptr)) | |
2460 | need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +k" : | |
2461 | "MODE -k"); | |
2462 | return; | |
2463 | } | |
2464 | ||
2465 | if (!state->mbuf) | |
2466 | return; | |
2467 | ||
2468 | /* Skip if this is a burst, we have a key already and the new key is | |
2469 | * after the old one alphabetically */ | |
2470 | if ((state->flags & MODE_PARSE_BURST) && | |
2471 | *(state->chptr->mode.key) && | |
2472 | ircd_strcmp(state->chptr->mode.key, t_str) <= 0) | |
2473 | return; | |
2474 | ||
2475 | /* can't add a key if one is set, nor can one remove the wrong key */ | |
2476 | if (!(state->flags & MODE_PARSE_FORCE)) | |
2477 | if ((state->dir == MODE_ADD && *state->chptr->mode.key) || | |
2478 | (state->dir == MODE_DEL && | |
2479 | ircd_strcmp(state->chptr->mode.key, t_str))) { | |
2480 | send_reply(state->sptr, ERR_KEYSET, state->chptr->chname); | |
2481 | return; | |
2482 | } | |
2483 | ||
2484 | if (!(state->flags & MODE_PARSE_WIPEOUT) && state->dir == MODE_ADD && | |
2485 | !ircd_strcmp(state->chptr->mode.key, t_str)) | |
2486 | return; /* no key change */ | |
2487 | ||
2488 | if (state->flags & MODE_PARSE_BOUNCE) { | |
2489 | if (*state->chptr->mode.key) /* reset old key */ | |
2490 | modebuf_mode_string(state->mbuf, MODE_DEL | flag_p[0], | |
2491 | state->chptr->mode.key, 0); | |
2492 | else /* remove new bogus key */ | |
2493 | modebuf_mode_string(state->mbuf, MODE_ADD | flag_p[0], t_str, 0); | |
2494 | } else /* send new key */ | |
2495 | modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0); | |
2496 | ||
2497 | if (state->flags & MODE_PARSE_SET) { | |
2498 | if (state->dir == MODE_DEL) /* remove the old key */ | |
2499 | *state->chptr->mode.key = '\0'; | |
9f8856e9 | 2500 | else |
189935b1 | 2501 | ircd_strncpy(state->chptr->mode.key, t_str, KEYLEN); |
2502 | } | |
2503 | } | |
2504 | ||
2505 | /* | |
2506 | * Helper function to convert user passes | |
2507 | */ | |
2508 | static void | |
2509 | mode_parse_upass(struct ParseState *state, int *flag_p) | |
2510 | { | |
2511 | char *t_str; | |
2512 | ||
2513 | if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */ | |
2514 | return; | |
2515 | ||
2516 | if (state->parc <= 0) { /* warn if not enough args */ | |
2517 | if (MyUser(state->sptr)) | |
2518 | need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +U" : | |
2519 | "MODE -U"); | |
2520 | return; | |
2521 | } | |
2522 | ||
2523 | t_str = state->parv[state->args_used++]; /* grab arg */ | |
2524 | state->parc--; | |
2525 | state->max_args--; | |
2526 | ||
2527 | /* If they're not an oper, they can't change modes */ | |
2528 | if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) { | |
2529 | send_notoper(state); | |
2530 | return; | |
2531 | } | |
2532 | ||
2533 | /* If a non-service user is trying to force it, refuse. */ | |
2534 | if (state->flags & MODE_PARSE_FORCE && MyUser(state->sptr) | |
2535 | && !HasPriv(state->sptr, PRIV_APASS_OPMODE)) { | |
2536 | send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname, | |
2537 | state->chptr->chname); | |
2538 | return; | |
2539 | } | |
2540 | ||
2541 | /* If they are not the channel manager, they are not allowed to change it */ | |
2542 | if (MyUser(state->sptr) && !(state->flags & MODE_PARSE_FORCE || IsChannelManager(state->member))) { | |
2543 | if (*state->chptr->mode.apass) { | |
2544 | send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname, | |
2545 | state->chptr->chname); | |
2546 | } else { | |
052b069e | 2547 | send_reply(state->sptr, ERR_NOMANAGER, state->chptr->chname, |
2548 | (TStime() - state->chptr->creationtime < 172800) ? | |
2549 | "approximately 4-5 minutes" : "approximately 48 hours"); | |
189935b1 | 2550 | } |
2551 | return; | |
2552 | } | |
2553 | ||
9fe6dff2 | 2554 | /* allow removing and then adding upass, but not adding and then removing */ |
2555 | if (state->dir == MODE_ADD) | |
2556 | { | |
2557 | if (state->done & DONE_UPASS_ADD) | |
2558 | return; | |
2559 | state->done |= DONE_UPASS_ADD; | |
2560 | } | |
2561 | else | |
2562 | { | |
2563 | if (state->done & (DONE_UPASS_ADD | DONE_UPASS_DEL)) | |
2564 | return; | |
2565 | state->done |= DONE_UPASS_DEL; | |
2566 | } | |
189935b1 | 2567 | |
2568 | /* clean up the upass string */ | |
2569 | clean_key(t_str); | |
2570 | if (!*t_str || *t_str == ':') { /* warn if empty */ | |
2571 | if (MyUser(state->sptr)) | |
2572 | need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +U" : | |
2573 | "MODE -U"); | |
2574 | return; | |
2575 | } | |
2576 | ||
2577 | if (!state->mbuf) | |
2578 | return; | |
2579 | ||
2580 | if (!(state->flags & MODE_PARSE_FORCE)) { | |
2581 | /* can't add the upass while apass is not set */ | |
2582 | if (state->dir == MODE_ADD && !*state->chptr->mode.apass) { | |
2583 | send_reply(state->sptr, ERR_UPASSNOTSET, state->chptr->chname, state->chptr->chname); | |
2584 | return; | |
2585 | } | |
2586 | /* cannot set a +U password that is the same as +A */ | |
2587 | if (state->dir == MODE_ADD && !ircd_strcmp(state->chptr->mode.apass, t_str)) { | |
2588 | send_reply(state->sptr, ERR_UPASS_SAME_APASS, state->chptr->chname); | |
2589 | return; | |
2590 | } | |
2591 | /* can't add a upass if one is set, nor can one remove the wrong upass */ | |
2592 | if ((state->dir == MODE_ADD && *state->chptr->mode.upass) || | |
2593 | (state->dir == MODE_DEL && | |
2594 | ircd_strcmp(state->chptr->mode.upass, t_str))) { | |
2595 | send_reply(state->sptr, ERR_KEYSET, state->chptr->chname); | |
2596 | return; | |
2597 | } | |
2598 | } | |
2599 | ||
2600 | if (!(state->flags & MODE_PARSE_WIPEOUT) && state->dir == MODE_ADD && | |
2601 | !ircd_strcmp(state->chptr->mode.upass, t_str)) | |
2602 | return; /* no upass change */ | |
2603 | ||
9f8856e9 | 2604 | /* Skip if this is a burst, we have a Upass already and the new Upass is |
2605 | * after the old one alphabetically */ | |
2606 | if ((state->flags & MODE_PARSE_BURST) && | |
2607 | *(state->chptr->mode.upass) && | |
2608 | ircd_strcmp(state->chptr->mode.upass, t_str) <= 0) | |
2609 | return; | |
2610 | ||
189935b1 | 2611 | if (state->flags & MODE_PARSE_BOUNCE) { |
2612 | if (*state->chptr->mode.upass) /* reset old upass */ | |
2613 | modebuf_mode_string(state->mbuf, MODE_DEL | flag_p[0], | |
2614 | state->chptr->mode.upass, 0); | |
2615 | else /* remove new bogus upass */ | |
2616 | modebuf_mode_string(state->mbuf, MODE_ADD | flag_p[0], t_str, 0); | |
2617 | } else /* send new upass */ | |
2618 | modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0); | |
2619 | ||
2620 | if (state->flags & MODE_PARSE_SET) { | |
2621 | if (state->dir == MODE_DEL) /* remove the old upass */ | |
2622 | *state->chptr->mode.upass = '\0'; | |
9f8856e9 | 2623 | else |
189935b1 | 2624 | ircd_strncpy(state->chptr->mode.upass, t_str, KEYLEN); |
2625 | } | |
2626 | } | |
2627 | ||
2628 | /* | |
2629 | * Helper function to convert admin passes | |
2630 | */ | |
2631 | static void | |
2632 | mode_parse_apass(struct ParseState *state, int *flag_p) | |
2633 | { | |
2634 | struct Membership *memb; | |
2635 | char *t_str; | |
2636 | ||
2637 | if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */ | |
2638 | return; | |
2639 | ||
2640 | if (state->parc <= 0) { /* warn if not enough args */ | |
2641 | if (MyUser(state->sptr)) | |
2642 | need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +A" : | |
2643 | "MODE -A"); | |
2644 | return; | |
2645 | } | |
2646 | ||
2647 | t_str = state->parv[state->args_used++]; /* grab arg */ | |
2648 | state->parc--; | |
2649 | state->max_args--; | |
2650 | ||
2651 | /* If they're not an oper, they can't change modes */ | |
2652 | if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) { | |
2653 | send_notoper(state); | |
2654 | return; | |
2655 | } | |
2656 | ||
052b069e | 2657 | if (MyUser(state->sptr)) { |
2658 | if (state->flags & MODE_PARSE_FORCE) { | |
2659 | /* If an unprivileged oper is trying to force it, refuse. */ | |
2660 | if (!HasPriv(state->sptr, PRIV_APASS_OPMODE)) { | |
2661 | send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname, | |
2662 | state->chptr->chname); | |
2663 | return; | |
2664 | } | |
d8e74551 | 2665 | } else { |
052b069e | 2666 | /* If they are not the channel manager, they are not allowed to change it. */ |
2667 | if (!IsChannelManager(state->member)) { | |
2668 | if (*state->chptr->mode.apass) { | |
2669 | send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname, | |
2670 | state->chptr->chname); | |
2671 | } else { | |
2672 | send_reply(state->sptr, ERR_NOMANAGER, state->chptr->chname, | |
2673 | (TStime() - state->chptr->creationtime < 172800) ? | |
2674 | "approximately 4-5 minutes" : "approximately 48 hours"); | |
2675 | } | |
2676 | return; | |
2677 | } | |
2678 | /* Can't remove the Apass while Upass is still set. */ | |
2679 | if (state->dir == MODE_DEL && *state->chptr->mode.upass) { | |
2680 | send_reply(state->sptr, ERR_UPASSSET, state->chptr->chname, state->chptr->chname); | |
2681 | return; | |
2682 | } | |
2683 | /* Can't add an Apass if one is set, nor can one remove the wrong Apass. */ | |
2684 | if ((state->dir == MODE_ADD && *state->chptr->mode.apass) || | |
2685 | (state->dir == MODE_DEL && ircd_strcmp(state->chptr->mode.apass, t_str))) { | |
2686 | send_reply(state->sptr, ERR_KEYSET, state->chptr->chname); | |
2687 | return; | |
2688 | } | |
2689 | } | |
2690 | ||
2691 | /* Forbid removing the Apass if the channel is older than 48 hours | |
2692 | * unless an oper is doing it. */ | |
2693 | if (TStime() - state->chptr->creationtime >= 172800 | |
2694 | && state->dir == MODE_DEL | |
2695 | && !IsAnOper(state->sptr)) { | |
2696 | send_reply(state->sptr, ERR_CHANSECURED, state->chptr->chname); | |
2697 | return; | |
189935b1 | 2698 | } |
189935b1 | 2699 | } |
2700 | ||
9fe6dff2 | 2701 | /* allow removing and then adding apass, but not adding and then removing */ |
2702 | if (state->dir == MODE_ADD) | |
2703 | { | |
2704 | if (state->done & DONE_APASS_ADD) | |
2705 | return; | |
2706 | state->done |= DONE_APASS_ADD; | |
2707 | } | |
2708 | else | |
2709 | { | |
2710 | if (state->done & (DONE_APASS_ADD | DONE_APASS_DEL)) | |
2711 | return; | |
2712 | state->done |= DONE_APASS_DEL; | |
2713 | } | |
189935b1 | 2714 | |
2715 | /* clean up the apass string */ | |
2716 | clean_key(t_str); | |
2717 | if (!*t_str || *t_str == ':') { /* warn if empty */ | |
2718 | if (MyUser(state->sptr)) | |
2719 | need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +A" : | |
2720 | "MODE -A"); | |
2721 | return; | |
2722 | } | |
2723 | ||
2724 | if (!state->mbuf) | |
2725 | return; | |
2726 | ||
189935b1 | 2727 | if (!(state->flags & MODE_PARSE_WIPEOUT) && state->dir == MODE_ADD && |
2728 | !ircd_strcmp(state->chptr->mode.apass, t_str)) | |
2729 | return; /* no apass change */ | |
2730 | ||
9f8856e9 | 2731 | /* Skip if this is a burst, we have an Apass already and the new Apass is |
2732 | * after the old one alphabetically */ | |
2733 | if ((state->flags & MODE_PARSE_BURST) && | |
2734 | *(state->chptr->mode.apass) && | |
2735 | ircd_strcmp(state->chptr->mode.apass, t_str) <= 0) | |
2736 | return; | |
2737 | ||
189935b1 | 2738 | if (state->flags & MODE_PARSE_BOUNCE) { |
2739 | if (*state->chptr->mode.apass) /* reset old apass */ | |
2740 | modebuf_mode_string(state->mbuf, MODE_DEL | flag_p[0], | |
2741 | state->chptr->mode.apass, 0); | |
2742 | else /* remove new bogus apass */ | |
2743 | modebuf_mode_string(state->mbuf, MODE_ADD | flag_p[0], t_str, 0); | |
2744 | } else /* send new apass */ | |
2745 | modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0); | |
2746 | ||
2747 | if (state->flags & MODE_PARSE_SET) { | |
2748 | if (state->dir == MODE_ADD) { /* set the new apass */ | |
9f8856e9 | 2749 | /* Only accept the new apass if there is no current apass or |
2750 | * this is a BURST. */ | |
2751 | if (state->chptr->mode.apass[0] == '\0' || | |
2752 | (state->flags & MODE_PARSE_BURST)) | |
189935b1 | 2753 | ircd_strncpy(state->chptr->mode.apass, t_str, KEYLEN); |
2754 | /* Make it VERY clear to the user that this is a one-time password */ | |
2755 | if (MyUser(state->sptr)) { | |
2756 | send_reply(state->sptr, RPL_APASSWARN_SET, state->chptr->mode.apass); | |
2757 | send_reply(state->sptr, RPL_APASSWARN_SECRET, state->chptr->chname, | |
2758 | state->chptr->mode.apass); | |
2759 | } | |
052b069e | 2760 | /* Give the channel manager level 0 ops. |
2761 | There should not be tested for IsChannelManager here because | |
2762 | on the local server it is impossible to set the apass if one | |
2763 | isn't a channel manager and remote servers might need to sync | |
2764 | the oplevel here: when someone creates a channel (and becomes | |
2765 | channel manager) during a net.break, and only sets the Apass | |
2766 | after the net rejoined, they will have oplevel MAXOPLEVEL on | |
2767 | all remote servers. */ | |
2768 | if (state->member) | |
189935b1 | 2769 | SetOpLevel(state->member, 0); |
2770 | } else { /* remove the old apass */ | |
2771 | *state->chptr->mode.apass = '\0'; | |
052b069e | 2772 | /* Clear Upass so that there is never a Upass set when a zannel is burst. */ |
2773 | *state->chptr->mode.upass = '\0'; | |
189935b1 | 2774 | if (MyUser(state->sptr)) |
2775 | send_reply(state->sptr, RPL_APASSWARN_CLEAR); | |
2776 | /* Revert everyone to MAXOPLEVEL. */ | |
2777 | for (memb = state->chptr->members; memb; memb = memb->next_member) { | |
2778 | if (memb->status & MODE_CHANOP) | |
2779 | SetOpLevel(memb, MAXOPLEVEL); | |
2780 | } | |
2781 | } | |
2782 | } | |
2783 | } | |
2784 | ||
2785 | /** Compare one ban's extent to another. | |
2786 | * This works very similarly to mmatch() but it knows about CIDR masks | |
2787 | * and ban exceptions. If both bans are CIDR-based, compare their | |
2788 | * address bits; otherwise, use mmatch(). | |
2789 | * @param[in] old_ban One ban. | |
2790 | * @param[in] new_ban Another ban. | |
2791 | * @return Zero if \a old_ban is a superset of \a new_ban, non-zero otherwise. | |
2792 | */ | |
2793 | static int | |
2794 | bmatch(struct Ban *old_ban, struct Ban *new_ban) | |
2795 | { | |
2796 | int res; | |
2797 | assert(old_ban != NULL); | |
2798 | assert(new_ban != NULL); | |
2799 | /* A ban is never treated as a superset of an exception. */ | |
2800 | if (!(old_ban->flags & BAN_EXCEPTION) | |
2801 | && (new_ban->flags & BAN_EXCEPTION)) | |
2802 | return 1; | |
2803 | /* If either is not an address mask, match the text masks. */ | |
2804 | if ((old_ban->flags & new_ban->flags & BAN_IPMASK) == 0) | |
2805 | return mmatch(old_ban->banstr, new_ban->banstr); | |
2806 | /* If the old ban has a longer prefix than new, it cannot be a superset. */ | |
2807 | if (old_ban->addrbits > new_ban->addrbits) | |
2808 | return 1; | |
2809 | /* Compare the masks before the hostname part. */ | |
2810 | old_ban->banstr[old_ban->nu_len] = new_ban->banstr[new_ban->nu_len] = '\0'; | |
2811 | res = mmatch(old_ban->banstr, new_ban->banstr); | |
2812 | old_ban->banstr[old_ban->nu_len] = new_ban->banstr[new_ban->nu_len] = '@'; | |
2813 | if (res) | |
2814 | return res; | |
9f8856e9 | 2815 | /* If the old ban's mask mismatches, cannot be a superset. */ |
2816 | if (!ipmask_check(&new_ban->address, &old_ban->address, old_ban->addrbits)) | |
2817 | return 1; | |
2818 | /* Otherwise it depends on whether the old ban's text is a superset | |
2819 | * of the new. */ | |
2820 | return mmatch(old_ban->banstr, new_ban->banstr); | |
189935b1 | 2821 | } |
2822 | ||
2823 | /** Add a ban from a ban list and mark bans that should be removed | |
2824 | * because they overlap. | |
2825 | * | |
2826 | * There are three invariants for a ban list. First, no ban may be | |
2827 | * more specific than another ban. Second, no exception may be more | |
2828 | * specific than another exception. Finally, no ban may be more | |
2829 | * specific than any exception. | |
2830 | * | |
2831 | * @param[in,out] banlist Pointer to head of list. | |
2832 | * @param[in] newban Ban (or exception) to add (or remove). | |
2833 | * @param[in] do_free If non-zero, free \a newban on failure. | |
2834 | * @return Zero if \a newban could be applied, non-zero if not. | |
2835 | */ | |
2836 | int apply_ban(struct Ban **banlist, struct Ban *newban, int do_free) | |
2837 | { | |
2838 | struct Ban *ban; | |
2839 | size_t count = 0; | |
2840 | ||
2841 | assert(newban->flags & (BAN_ADD|BAN_DEL)); | |
2842 | if (newban->flags & BAN_ADD) { | |
2843 | size_t totlen = 0; | |
a351d4e2 | 2844 | /* If a less specific *active* entry is found, fail. */ |
189935b1 | 2845 | for (ban = *banlist; ban; ban = ban->next) { |
a351d4e2 | 2846 | if (!bmatch(ban, newban) && !(ban->flags & BAN_DEL)) { |
189935b1 | 2847 | if (do_free) |
2848 | free_ban(newban); | |
2849 | return 1; | |
2850 | } | |
2851 | if (!(ban->flags & (BAN_OVERLAPPED|BAN_DEL))) { | |
2852 | count++; | |
2853 | totlen += strlen(ban->banstr); | |
2854 | } | |
2855 | } | |
2856 | /* Mark more specific entries and add this one to the end of the list. */ | |
2857 | while ((ban = *banlist) != NULL) { | |
2858 | if (!bmatch(newban, ban)) { | |
2859 | ban->flags |= BAN_OVERLAPPED | BAN_DEL; | |
2860 | } | |
2861 | banlist = &ban->next; | |
2862 | } | |
2863 | *banlist = newban; | |
2864 | return 0; | |
2865 | } else if (newban->flags & BAN_DEL) { | |
2866 | size_t remove_count = 0; | |
2867 | /* Mark more specific entries. */ | |
2868 | for (ban = *banlist; ban; ban = ban->next) { | |
2869 | if (!bmatch(newban, ban)) { | |
2870 | ban->flags |= BAN_OVERLAPPED | BAN_DEL; | |
2871 | remove_count++; | |
2872 | } | |
2873 | } | |
2874 | if (remove_count) | |
2875 | return 0; | |
2876 | /* If no matches were found, fail. */ | |
2877 | if (do_free) | |
2878 | free_ban(newban); | |
2879 | return 3; | |
2880 | } | |
2881 | if (do_free) | |
2882 | free_ban(newban); | |
2883 | return 4; | |
2884 | } | |
2885 | ||
2886 | /* | |
2887 | * Helper function to convert bans | |
2888 | */ | |
2889 | static void | |
2890 | mode_parse_ban(struct ParseState *state, int *flag_p) | |
2891 | { | |
2892 | char *t_str, *s; | |
2893 | struct Ban *ban, *newban; | |
2894 | ||
2895 | if (state->parc <= 0) { /* Not enough args, send ban list */ | |
2896 | if (MyUser(state->sptr) && !(state->done & DONE_BANLIST)) { | |
2897 | send_ban_list(state->sptr, state->chptr); | |
2898 | state->done |= DONE_BANLIST; | |
2899 | } | |
2900 | ||
2901 | return; | |
2902 | } | |
2903 | ||
2904 | if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */ | |
2905 | return; | |
2906 | ||
2907 | t_str = state->parv[state->args_used++]; /* grab arg */ | |
2908 | state->parc--; | |
2909 | state->max_args--; | |
2910 | ||
2911 | /* If they're not an oper, they can't change modes */ | |
2912 | if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) { | |
2913 | send_notoper(state); | |
2914 | return; | |
2915 | } | |
2916 | ||
2917 | if ((s = strchr(t_str, ' '))) | |
2918 | *s = '\0'; | |
2919 | ||
2920 | if (!*t_str || *t_str == ':') { /* warn if empty */ | |
2921 | if (MyUser(state->sptr)) | |
2922 | need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +b" : | |
2923 | "MODE -b"); | |
2924 | return; | |
2925 | } | |
2926 | ||
2927 | /* Clear all ADD/DEL/OVERLAPPED flags from ban list. */ | |
2928 | if (!(state->done & DONE_BANCLEAN)) { | |
2929 | for (ban = state->chptr->banlist; ban; ban = ban->next) | |
2930 | ban->flags &= ~(BAN_ADD | BAN_DEL | BAN_OVERLAPPED); | |
2931 | state->done |= DONE_BANCLEAN; | |
2932 | } | |
2933 | ||
2934 | /* remember the ban for the moment... */ | |
2935 | newban = state->banlist + (state->numbans++); | |
2936 | newban->next = 0; | |
2937 | newban->flags = ((state->dir == MODE_ADD) ? BAN_ADD : BAN_DEL) | |
2938 | | (*flag_p == MODE_BAN ? 0 : BAN_EXCEPTION); | |
2939 | set_ban_mask(newban, collapse(pretty_mask(t_str))); | |
2940 | ircd_strncpy(newban->who, IsUser(state->sptr) ? cli_name(state->sptr) : "*", NICKLEN); | |
2941 | newban->when = TStime(); | |
2942 | apply_ban(&state->chptr->banlist, newban, 0); | |
2943 | } | |
2944 | ||
2945 | /* | |
2946 | * This is the bottom half of the ban processor | |
2947 | */ | |
2948 | static void | |
2949 | mode_process_bans(struct ParseState *state) | |
2950 | { | |
2951 | struct Ban *ban, *newban, *prevban, *nextban; | |
2952 | int count = 0; | |
2953 | int len = 0; | |
2954 | int banlen; | |
2955 | int changed = 0; | |
2956 | ||
2957 | for (prevban = 0, ban = state->chptr->banlist; ban; ban = nextban) { | |
2958 | count++; | |
2959 | banlen = strlen(ban->banstr); | |
2960 | len += banlen; | |
2961 | nextban = ban->next; | |
2962 | ||
2963 | if ((ban->flags & (BAN_DEL | BAN_ADD)) == (BAN_DEL | BAN_ADD)) { | |
2964 | if (prevban) | |
2965 | prevban->next = 0; /* Break the list; ban isn't a real ban */ | |
2966 | else | |
2967 | state->chptr->banlist = 0; | |
2968 | ||
2969 | count--; | |
2970 | len -= banlen; | |
2971 | ||
2972 | continue; | |
2973 | } else if (ban->flags & BAN_DEL) { /* Deleted a ban? */ | |
2974 | char *bandup; | |
2975 | DupString(bandup, ban->banstr); | |
2976 | modebuf_mode_string(state->mbuf, MODE_DEL | MODE_BAN, | |
2977 | bandup, 1); | |
2978 | ||
2979 | if (state->flags & MODE_PARSE_SET) { /* Ok, make it take effect */ | |
2980 | if (prevban) /* clip it out of the list... */ | |
2981 | prevban->next = ban->next; | |
2982 | else | |
2983 | state->chptr->banlist = ban->next; | |
2984 | ||
2985 | count--; | |
2986 | len -= banlen; | |
2987 | free_ban(ban); | |
2988 | ||
2989 | changed++; | |
2990 | continue; /* next ban; keep prevban like it is */ | |
2991 | } else | |
2992 | ban->flags &= BAN_IPMASK; /* unset other flags */ | |
2993 | } else if (ban->flags & BAN_ADD) { /* adding a ban? */ | |
2994 | if (prevban) | |
2995 | prevban->next = 0; /* Break the list; ban isn't a real ban */ | |
2996 | else | |
2997 | state->chptr->banlist = 0; | |
2998 | ||
2999 | /* If we're supposed to ignore it, do so. */ | |
3000 | if (ban->flags & BAN_OVERLAPPED && | |
3001 | !(state->flags & MODE_PARSE_BOUNCE)) { | |
3002 | count--; | |
3003 | len -= banlen; | |
3004 | } else { | |
3005 | if (state->flags & MODE_PARSE_SET && MyUser(state->sptr) && | |
3006 | (len > (feature_int(FEAT_AVBANLEN) * feature_int(FEAT_MAXBANS)) || | |
3007 | count > feature_int(FEAT_MAXBANS))) { | |
3008 | send_reply(state->sptr, ERR_BANLISTFULL, state->chptr->chname, | |
3009 | ban->banstr); | |
3010 | count--; | |
3011 | len -= banlen; | |
3012 | } else { | |
3013 | char *bandup; | |
3014 | /* add the ban to the buffer */ | |
3015 | DupString(bandup, ban->banstr); | |
3016 | modebuf_mode_string(state->mbuf, MODE_ADD | MODE_BAN, | |
3017 | bandup, 1); | |
3018 | ||
3019 | if (state->flags & MODE_PARSE_SET) { /* create a new ban */ | |
3020 | newban = make_ban(ban->banstr); | |
3021 | strcpy(newban->who, ban->who); | |
3022 | newban->when = ban->when; | |
3023 | newban->flags = ban->flags & BAN_IPMASK; | |
3024 | ||
3025 | newban->next = state->chptr->banlist; /* and link it in */ | |
3026 | state->chptr->banlist = newban; | |
3027 | ||
3028 | changed++; | |
3029 | } | |
3030 | } | |
3031 | } | |
3032 | } | |
3033 | ||
3034 | prevban = ban; | |
3035 | } /* for (prevban = 0, ban = state->chptr->banlist; ban; ban = nextban) { */ | |
3036 | ||
3037 | if (changed) /* if we changed the ban list, we must invalidate the bans */ | |
3038 | mode_ban_invalidate(state->chptr); | |
3039 | } | |
3040 | ||
3041 | /* | |
3042 | * Helper function to process client changes | |
3043 | */ | |
3044 | static void | |
3045 | mode_parse_client(struct ParseState *state, int *flag_p) | |
3046 | { | |
3047 | char *t_str; | |
052b069e | 3048 | char *colon; |
189935b1 | 3049 | struct Client *acptr; |
3050 | struct Membership *member; | |
3051 | int oplevel = MAXOPLEVEL + 1; | |
052b069e | 3052 | int req_oplevel; |
189935b1 | 3053 | int i; |
3054 | ||
3055 | if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */ | |
3056 | return; | |
3057 | ||
3058 | if (state->parc <= 0) /* return if not enough args */ | |
3059 | return; | |
3060 | ||
3061 | t_str = state->parv[state->args_used++]; /* grab arg */ | |
3062 | state->parc--; | |
3063 | state->max_args--; | |
3064 | ||
3065 | /* If they're not an oper, they can't change modes */ | |
3066 | if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) { | |
3067 | send_notoper(state); | |
3068 | return; | |
3069 | } | |
3070 | ||
052b069e | 3071 | if (MyUser(state->sptr)) { |
3072 | colon = strchr(t_str, ':'); | |
3073 | if (colon != NULL) { | |
3074 | *colon++ = '\0'; | |
3075 | req_oplevel = atoi(colon); | |
3076 | if (!(state->flags & MODE_PARSE_FORCE) | |
3077 | && state->member | |
3078 | && (req_oplevel < OpLevel(state->member) | |
3079 | || (req_oplevel == OpLevel(state->member) | |
3080 | && OpLevel(state->member) < MAXOPLEVEL) | |
3081 | || req_oplevel > MAXOPLEVEL)) | |
3082 | send_reply(state->sptr, ERR_NOTLOWEROPLEVEL, | |
3083 | t_str, state->chptr->chname, | |
3084 | OpLevel(state->member), req_oplevel, "op", | |
3085 | OpLevel(state->member) == req_oplevel ? "the same" : "a higher"); | |
3086 | else if (req_oplevel <= MAXOPLEVEL) | |
3087 | oplevel = req_oplevel; | |
3088 | } | |
3089 | /* find client we're manipulating */ | |
189935b1 | 3090 | acptr = find_chasing(state->sptr, t_str, NULL); |
052b069e | 3091 | } else { |
189935b1 | 3092 | if (t_str[5] == ':') { |
3093 | t_str[5] = '\0'; | |
3094 | oplevel = atoi(t_str + 6); | |
3095 | } | |
3096 | acptr = findNUser(t_str); | |
3097 | } | |
3098 | ||
3099 | if (!acptr) | |
3100 | return; /* find_chasing() already reported an error to the user */ | |
3101 | ||
3102 | for (i = 0; i < MAXPARA; i++) /* find an element to stick them in */ | |
3103 | if (!state->cli_change[i].flag || (state->cli_change[i].client == acptr && | |
3104 | state->cli_change[i].flag & flag_p[0])) | |
3105 | break; /* found a slot */ | |
3106 | ||
3107 | /* If we are going to bounce this deop, mark the correct oplevel. */ | |
3108 | if (state->flags & MODE_PARSE_BOUNCE | |
3109 | && state->dir == MODE_DEL | |
3110 | && flag_p[0] == MODE_CHANOP | |
3111 | && (member = find_member_link(state->chptr, acptr))) | |
3112 | oplevel = OpLevel(member); | |
3113 | ||
3114 | /* Store what we're doing to them */ | |
3115 | state->cli_change[i].flag = state->dir | flag_p[0]; | |
3116 | state->cli_change[i].oplevel = oplevel; | |
3117 | state->cli_change[i].client = acptr; | |
3118 | } | |
3119 | ||
3120 | /* | |
3121 | * Helper function to process the changed client list | |
3122 | */ | |
3123 | static void | |
3124 | mode_process_clients(struct ParseState *state) | |
3125 | { | |
3126 | int i; | |
3127 | struct Membership *member; | |
3128 | ||
3129 | for (i = 0; state->cli_change[i].flag; i++) { | |
3130 | assert(0 != state->cli_change[i].client); | |
3131 | ||
3132 | /* look up member link */ | |
3133 | if (!(member = find_member_link(state->chptr, | |
3134 | state->cli_change[i].client)) || | |
3135 | (MyUser(state->sptr) && IsZombie(member))) { | |
3136 | if (MyUser(state->sptr)) | |
3137 | send_reply(state->sptr, ERR_USERNOTINCHANNEL, | |
3138 | cli_name(state->cli_change[i].client), | |
3139 | state->chptr->chname); | |
3140 | continue; | |
3141 | } | |
3142 | ||
3143 | if ((state->cli_change[i].flag & MODE_ADD && | |
3144 | (state->cli_change[i].flag & member->status)) || | |
3145 | (state->cli_change[i].flag & MODE_DEL && | |
3146 | !(state->cli_change[i].flag & member->status))) | |
3147 | continue; /* no change made, don't do anything */ | |
3148 | ||
3149 | /* see if the deop is allowed */ | |
3150 | if ((state->cli_change[i].flag & (MODE_DEL | MODE_CHANOP)) == | |
3151 | (MODE_DEL | MODE_CHANOP)) { | |
3152 | /* prevent +k users from being deopped */ | |
d8e74551 | 3153 | /* |
3154 | * ASUKA_X: | |
3155 | * Allow +X'ed users to mess with +k'ed. | |
3156 | * --Bigfoot | |
3157 | */ | |
3158 | if ((IsChannelService(state->cli_change[i].client) && IsService(cli_user(state->cli_change[i].client)->server)) || (IsChannelService(state->cli_change[i].client) && !IsXtraOp(state->sptr))) { | |
189935b1 | 3159 | if (state->flags & MODE_PARSE_FORCE) /* it was forced */ |
3160 | sendto_opmask_butone(0, SNO_HACK4, "Deop of +k user on %H by %s", | |
3161 | state->chptr, | |
3162 | (IsServer(state->sptr) ? cli_name(state->sptr) : | |
3163 | cli_name((cli_user(state->sptr))->server))); | |
3164 | ||
d8e74551 | 3165 | else if (MyUser(state->sptr) && state->flags & MODE_PARSE_SET && (state->sptr != state->cli_change[i].client)) { |
3166 | if(IsService(cli_user(state->cli_change[i].client)->server) && IsChannelService(state->cli_change[i].client)){ | |
3167 | send_reply(state->sptr, ERR_ISREALSERVICE, | |
3168 | cli_name(state->cli_change[i].client), | |
3169 | state->chptr->chname); | |
3170 | }else{ | |
3171 | send_reply(state->sptr, ERR_ISCHANSERVICE, | |
3172 | cli_name(state->cli_change[i].client), | |
3173 | state->chptr->chname); | |
3174 | } | |
3175 | ||
189935b1 | 3176 | continue; |
3177 | } | |
3178 | } | |
3179 | ||
3180 | /* check deop for local user */ | |
3181 | if (MyUser(state->sptr)) { | |
3182 | ||
3183 | /* don't allow local opers to be deopped on local channels */ | |
3184 | if (state->cli_change[i].client != state->sptr && | |
3185 | IsLocalChannel(state->chptr->chname) && | |
3186 | HasPriv(state->cli_change[i].client, PRIV_DEOP_LCHAN)) { | |
3187 | send_reply(state->sptr, ERR_ISOPERLCHAN, | |
3188 | cli_name(state->cli_change[i].client), | |
3189 | state->chptr->chname); | |
3190 | continue; | |
3191 | } | |
3192 | ||
052b069e | 3193 | /* Forbid deopping other members with an oplevel less than |
3194 | * one's own level, and other members with an oplevel the same | |
3195 | * as one's own unless both are at MAXOPLEVEL. */ | |
3196 | if (state->sptr != state->cli_change[i].client | |
189935b1 | 3197 | && state->member |
052b069e | 3198 | && ((OpLevel(member) < OpLevel(state->member)) |
3199 | || (OpLevel(member) == OpLevel(state->member) | |
3200 | && OpLevel(member) < MAXOPLEVEL))) { | |
189935b1 | 3201 | int equal = (OpLevel(member) == OpLevel(state->member)); |
3202 | send_reply(state->sptr, ERR_NOTLOWEROPLEVEL, | |
3203 | cli_name(state->cli_change[i].client), | |
3204 | state->chptr->chname, | |
3205 | OpLevel(state->member), OpLevel(member), | |
3206 | "deop", equal ? "the same" : "a higher"); | |
3207 | continue; | |
3208 | } | |
3209 | } | |
3210 | } | |
3211 | ||
3212 | /* set op-level of member being opped */ | |
3213 | if ((state->cli_change[i].flag & (MODE_ADD | MODE_CHANOP)) == | |
3214 | (MODE_ADD | MODE_CHANOP)) { | |
3215 | /* If a valid oplevel was specified, use it. | |
3216 | * Otherwise, if being opped by an outsider, get MAXOPLEVEL. | |
3217 | * Otherwise, if not an apass channel, or state->member has | |
3218 | * MAXOPLEVEL, get oplevel MAXOPLEVEL. | |
3219 | * Otherwise, get state->member's oplevel+1. | |
3220 | */ | |
606112af | 3221 | if (state->cli_change[i].oplevel <= MAXOPLEVEL) { |
a0322b26 | 3222 | if ((IsChannelService(state->sptr) && IsService(cli_user(state->sptr)->server)) || (IsService(state->sptr))) { |
606112af | 3223 | SetOpLevel(member, state->cli_change[i].oplevel); |
3224 | } else { | |
3225 | SetOpLevel(member, state->cli_change[i].oplevel > MINOPLEVEL ? state->cli_change[i].oplevel : MINOPLEVEL); | |
3226 | } | |
3227 | } | |
189935b1 | 3228 | else if (!state->member) |
3229 | SetOpLevel(member, MAXOPLEVEL); | |
0c466275 | 3230 | else if (OpLevel(state->member) >= MAXOPLEVEL) |
3231 | SetOpLevel(member, OpLevel(state->member)); | |
189935b1 | 3232 | else |
606112af | 3233 | SetOpLevel(member, OpLevel(state->member) >= MINOPLEVEL ? OpLevel(state->member) + 1 : MINOPLEVEL); |
189935b1 | 3234 | } |
3235 | ||
3236 | /* actually effect the change */ | |
3237 | if (state->flags & MODE_PARSE_SET) { | |
3238 | if (state->cli_change[i].flag & MODE_ADD) { | |
052b069e | 3239 | if (IsDelayedJoin(member) && !IsZombie(member)) |
189935b1 | 3240 | RevealDelayedJoin(member); |
3241 | member->status |= (state->cli_change[i].flag & | |
3242 | (MODE_CHANOP | MODE_VOICE)); | |
3243 | if (state->cli_change[i].flag & MODE_CHANOP) | |
3244 | ClearDeopped(member); | |
3245 | } else | |
3246 | member->status &= ~(state->cli_change[i].flag & | |
3247 | (MODE_CHANOP | MODE_VOICE)); | |
3248 | } | |
3249 | ||
3250 | /* accumulate the change */ | |
3251 | modebuf_mode_client(state->mbuf, state->cli_change[i].flag, | |
3252 | state->cli_change[i].client, | |
3253 | state->cli_change[i].oplevel); | |
3254 | } /* for (i = 0; state->cli_change[i].flags; i++) */ | |
3255 | } | |
3256 | ||
3257 | /* | |
3258 | * Helper function to process the simple modes | |
3259 | */ | |
3260 | static void | |
3261 | mode_parse_mode(struct ParseState *state, int *flag_p) | |
3262 | { | |
3263 | /* If they're not an oper, they can't change modes */ | |
3264 | if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) { | |
3265 | send_notoper(state); | |
3266 | return; | |
3267 | } | |
3268 | ||
3269 | if (!state->mbuf) | |
3270 | return; | |
3271 | ||
3272 | if (state->dir == MODE_ADD) { | |
3273 | state->add |= flag_p[0]; | |
3274 | state->del &= ~flag_p[0]; | |
3275 | ||
3276 | if (flag_p[0] & MODE_SECRET) { | |
3277 | state->add &= ~MODE_PRIVATE; | |
3278 | state->del |= MODE_PRIVATE; | |
3279 | } else if (flag_p[0] & MODE_PRIVATE) { | |
3280 | state->add &= ~MODE_SECRET; | |
3281 | state->del |= MODE_SECRET; | |
3282 | } | |
189935b1 | 3283 | } else { |
3284 | state->add &= ~flag_p[0]; | |
3285 | state->del |= flag_p[0]; | |
3286 | } | |
3287 | ||
3288 | assert(0 == (state->add & state->del)); | |
3289 | assert((MODE_SECRET | MODE_PRIVATE) != | |
3290 | (state->add & (MODE_SECRET | MODE_PRIVATE))); | |
3291 | } | |
3292 | ||
3293 | /* | |
3294 | * This routine is intended to parse MODE or OPMODE commands and effect the | |
3295 | * changes (or just build the bounce buffer). We pass the starting offset | |
3296 | * as a | |
3297 | */ | |
3298 | int | |
3299 | mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr, | |
3300 | struct Channel *chptr, int parc, char *parv[], unsigned int flags, | |
3301 | struct Membership* member) | |
3302 | { | |
3303 | static int chan_flags[] = { | |
3304 | MODE_CHANOP, 'o', | |
3305 | MODE_VOICE, 'v', | |
3306 | MODE_PRIVATE, 'p', | |
3307 | MODE_SECRET, 's', | |
3308 | MODE_MODERATED, 'm', | |
3309 | MODE_TOPICLIMIT, 't', | |
3310 | MODE_INVITEONLY, 'i', | |
3311 | MODE_NOPRIVMSGS, 'n', | |
3312 | MODE_KEY, 'k', | |
3313 | MODE_APASS, 'A', | |
3314 | MODE_UPASS, 'U', | |
3315 | MODE_BAN, 'b', | |
3316 | MODE_LIMIT, 'l', | |
3317 | MODE_REGONLY, 'r', | |
3318 | MODE_DELJOINS, 'D', | |
d8e74551 | 3319 | MODE_NOQUITPARTS, 'u', |
3320 | MODE_NOCOLOUR, 'c', | |
3321 | MODE_NOCTCP, 'C', | |
3322 | MODE_NONOTICE, 'N', | |
1cade670 | 3323 | MODE_NOMULTITARGET, 'T', |
3324 | MODE_MODERATENOREG, 'M', | |
189935b1 | 3325 | MODE_ADD, '+', |
3326 | MODE_DEL, '-', | |
3327 | 0x0, 0x0 | |
3328 | }; | |
3329 | int i; | |
3330 | int *flag_p; | |
3331 | unsigned int t_mode; | |
3332 | char *modestr; | |
3333 | struct ParseState state; | |
3334 | ||
3335 | assert(0 != cptr); | |
3336 | assert(0 != sptr); | |
3337 | assert(0 != chptr); | |
3338 | assert(0 != parc); | |
3339 | assert(0 != parv); | |
3340 | ||
3341 | state.mbuf = mbuf; | |
3342 | state.cptr = cptr; | |
3343 | state.sptr = sptr; | |
3344 | state.chptr = chptr; | |
3345 | state.member = member; | |
3346 | state.parc = parc; | |
3347 | state.parv = parv; | |
3348 | state.flags = flags; | |
3349 | state.dir = MODE_ADD; | |
3350 | state.done = 0; | |
3351 | state.add = 0; | |
3352 | state.del = 0; | |
3353 | state.args_used = 0; | |
3354 | state.max_args = MAXMODEPARAMS; | |
3355 | state.numbans = 0; | |
3356 | ||
3357 | for (i = 0; i < MAXPARA; i++) { /* initialize ops/voices arrays */ | |
3358 | state.banlist[i].next = 0; | |
3359 | state.banlist[i].who[0] = '\0'; | |
3360 | state.banlist[i].when = 0; | |
3361 | state.banlist[i].flags = 0; | |
3362 | state.cli_change[i].flag = 0; | |
3363 | state.cli_change[i].client = 0; | |
3364 | } | |
3365 | ||
3366 | modestr = state.parv[state.args_used++]; | |
3367 | state.parc--; | |
3368 | ||
3369 | while (*modestr) { | |
3370 | for (; *modestr; modestr++) { | |
3371 | for (flag_p = chan_flags; flag_p[0]; flag_p += 2) /* look up flag */ | |
3372 | if (flag_p[1] == *modestr) | |
3373 | break; | |
3374 | ||
3375 | if (!flag_p[0]) { /* didn't find it? complain and continue */ | |
3376 | if (MyUser(state.sptr)) | |
3377 | send_reply(state.sptr, ERR_UNKNOWNMODE, *modestr); | |
3378 | continue; | |
3379 | } | |
3380 | ||
3381 | switch (*modestr) { | |
3382 | case '+': /* switch direction to MODE_ADD */ | |
3383 | case '-': /* switch direction to MODE_DEL */ | |
3384 | state.dir = flag_p[0]; | |
3385 | break; | |
3386 | ||
3387 | case 'l': /* deal with limits */ | |
3388 | mode_parse_limit(&state, flag_p); | |
3389 | break; | |
3390 | ||
3391 | case 'k': /* deal with keys */ | |
3392 | mode_parse_key(&state, flag_p); | |
3393 | break; | |
3394 | ||
3395 | case 'A': /* deal with Admin passes */ | |
3396 | if (IsServer(cptr) || feature_bool(FEAT_OPLEVELS)) | |
3397 | mode_parse_apass(&state, flag_p); | |
3398 | break; | |
3399 | ||
3400 | case 'U': /* deal with user passes */ | |
3401 | if (IsServer(cptr) || feature_bool(FEAT_OPLEVELS)) | |
3402 | mode_parse_upass(&state, flag_p); | |
3403 | break; | |
3404 | ||
3405 | case 'b': /* deal with bans */ | |
3406 | mode_parse_ban(&state, flag_p); | |
3407 | break; | |
3408 | ||
3409 | case 'o': /* deal with ops/voice */ | |
3410 | case 'v': | |
3411 | mode_parse_client(&state, flag_p); | |
3412 | break; | |
3413 | ||
3414 | default: /* deal with other modes */ | |
3415 | mode_parse_mode(&state, flag_p); | |
3416 | break; | |
3417 | } /* switch (*modestr) */ | |
3418 | } /* for (; *modestr; modestr++) */ | |
3419 | ||
3420 | if (state.flags & MODE_PARSE_BURST) | |
3421 | break; /* don't interpret any more arguments */ | |
3422 | ||
3423 | if (state.parc > 0) { /* process next argument in string */ | |
3424 | modestr = state.parv[state.args_used++]; | |
3425 | state.parc--; | |
3426 | ||
3427 | /* is it a TS? */ | |
9f8856e9 | 3428 | if (IsServer(state.cptr) && !state.parc && IsDigit(*modestr)) { |
189935b1 | 3429 | time_t recv_ts; |
3430 | ||
3431 | if (!(state.flags & MODE_PARSE_SET)) /* don't set earlier TS if */ | |
3432 | break; /* we're then going to bounce the mode! */ | |
3433 | ||
3434 | recv_ts = atoi(modestr); | |
3435 | ||
3436 | if (recv_ts && recv_ts < state.chptr->creationtime) | |
3437 | state.chptr->creationtime = recv_ts; /* respect earlier TS */ | |
9f8856e9 | 3438 | else if (recv_ts > state.chptr->creationtime) { |
3439 | struct Client *sserv; | |
3440 | ||
3441 | /* Check whether the originating server has fully processed | |
3442 | * the burst to it. */ | |
3443 | sserv = state.cptr; | |
3444 | if (!IsServer(sserv)) | |
3445 | sserv = cli_user(sserv)->server; | |
3446 | if (IsBurstOrBurstAck(sserv)) { | |
3447 | /* This is a legal but unusual case; the source server | |
3448 | * probably just has not processed the BURST for this | |
3449 | * channel. It SHOULD wipe out all its modes soon, so | |
3450 | * silently ignore the mode change rather than send a | |
3451 | * bounce that could desync modes from our side (that | |
3452 | * have already been sent). | |
3453 | */ | |
3454 | state.mbuf->mb_add = 0; | |
3455 | state.mbuf->mb_rem = 0; | |
3456 | state.mbuf->mb_count = 0; | |
3457 | return state.args_used; | |
3458 | } else { | |
3459 | /* Server is desynced; bounce the mode and deop the source | |
3460 | * to fix it. */ | |
3461 | state.mbuf->mb_dest &= ~MODEBUF_DEST_CHANNEL; | |
3462 | state.mbuf->mb_dest |= MODEBUF_DEST_BOUNCE | MODEBUF_DEST_HACK2; | |
3463 | if (!IsServer(state.cptr)) | |
3464 | state.mbuf->mb_dest |= MODEBUF_DEST_DEOP; | |
3465 | } | |
3466 | } | |
189935b1 | 3467 | |
3468 | break; /* break out of while loop */ | |
3469 | } else if (state.flags & MODE_PARSE_STRICT || | |
3470 | (MyUser(state.sptr) && state.max_args <= 0)) { | |
3471 | state.parc++; /* we didn't actually gobble the argument */ | |
3472 | state.args_used--; | |
3473 | break; /* break out of while loop */ | |
3474 | } | |
3475 | } | |
3476 | } /* while (*modestr) */ | |
3477 | ||
3478 | /* | |
3479 | * the rest of the function finishes building resultant MODEs; if the | |
3480 | * origin isn't a member or an oper, skip it. | |
3481 | */ | |
3482 | if (!state.mbuf || state.flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) | |
3483 | return state.args_used; /* tell our parent how many args we gobbled */ | |
3484 | ||
3485 | t_mode = state.chptr->mode.mode; | |
3486 | ||
3487 | if (state.del & t_mode) { /* delete any modes to be deleted... */ | |
3488 | modebuf_mode(state.mbuf, MODE_DEL | (state.del & t_mode)); | |
3489 | ||
3490 | t_mode &= ~state.del; | |
3491 | } | |
3492 | if (state.add & ~t_mode) { /* add any modes to be added... */ | |
3493 | modebuf_mode(state.mbuf, MODE_ADD | (state.add & ~t_mode)); | |
3494 | ||
3495 | t_mode |= state.add; | |
3496 | } | |
3497 | ||
3498 | if (state.flags & MODE_PARSE_SET) { /* set the channel modes */ | |
3499 | if ((state.chptr->mode.mode & MODE_INVITEONLY) && | |
3500 | !(t_mode & MODE_INVITEONLY)) | |
3501 | mode_invite_clear(state.chptr); | |
3502 | ||
3503 | state.chptr->mode.mode = t_mode; | |
3504 | } | |
3505 | ||
3506 | if (state.flags & MODE_PARSE_WIPEOUT) { | |
3507 | if (state.chptr->mode.limit && !(state.done & DONE_LIMIT)) | |
3508 | modebuf_mode_uint(state.mbuf, MODE_DEL | MODE_LIMIT, | |
3509 | state.chptr->mode.limit); | |
9fe6dff2 | 3510 | if (*state.chptr->mode.key && !(state.done & DONE_KEY_DEL)) |
189935b1 | 3511 | modebuf_mode_string(state.mbuf, MODE_DEL | MODE_KEY, |
3512 | state.chptr->mode.key, 0); | |
9fe6dff2 | 3513 | if (*state.chptr->mode.upass && !(state.done & DONE_UPASS_DEL)) |
189935b1 | 3514 | modebuf_mode_string(state.mbuf, MODE_DEL | MODE_UPASS, |
3515 | state.chptr->mode.upass, 0); | |
9fe6dff2 | 3516 | if (*state.chptr->mode.apass && !(state.done & DONE_APASS_DEL)) |
189935b1 | 3517 | modebuf_mode_string(state.mbuf, MODE_DEL | MODE_APASS, |
3518 | state.chptr->mode.apass, 0); | |
3519 | } | |
3520 | ||
3521 | if (state.done & DONE_BANCLEAN) /* process bans */ | |
3522 | mode_process_bans(&state); | |
3523 | ||
3524 | /* process client changes */ | |
3525 | if (state.cli_change[0].flag) | |
3526 | mode_process_clients(&state); | |
3527 | ||
3528 | return state.args_used; /* tell our parent how many args we gobbled */ | |
3529 | } | |
3530 | ||
3531 | /* | |
3532 | * Initialize a join buffer | |
3533 | */ | |
3534 | void | |
3535 | joinbuf_init(struct JoinBuf *jbuf, struct Client *source, | |
3536 | struct Client *connect, unsigned int type, char *comment, | |
3537 | time_t create) | |
3538 | { | |
3539 | int i; | |
3540 | ||
3541 | assert(0 != jbuf); | |
3542 | assert(0 != source); | |
3543 | assert(0 != connect); | |
3544 | ||
3545 | jbuf->jb_source = source; /* just initialize struct JoinBuf */ | |
3546 | jbuf->jb_connect = connect; | |
3547 | jbuf->jb_type = type; | |
3548 | jbuf->jb_comment = comment; | |
3549 | jbuf->jb_create = create; | |
3550 | jbuf->jb_count = 0; | |
3551 | jbuf->jb_strlen = (((type == JOINBUF_TYPE_JOIN || | |
3552 | type == JOINBUF_TYPE_PART || | |
3553 | type == JOINBUF_TYPE_PARTALL) ? | |
3554 | STARTJOINLEN : STARTCREATELEN) + | |
3555 | (comment ? strlen(comment) + 2 : 0)); | |
3556 | ||
3557 | for (i = 0; i < MAXJOINARGS; i++) | |
3558 | jbuf->jb_channels[i] = 0; | |
3559 | } | |
3560 | ||
3561 | /* | |
3562 | * Add a channel to the join buffer | |
3563 | */ | |
3564 | void | |
3565 | joinbuf_join(struct JoinBuf *jbuf, struct Channel *chan, unsigned int flags) | |
3566 | { | |
3567 | unsigned int len; | |
3568 | int is_local; | |
3569 | ||
3570 | assert(0 != jbuf); | |
3571 | ||
3572 | if (!chan) { | |
3573 | sendcmdto_serv_butone(jbuf->jb_source, CMD_JOIN, jbuf->jb_connect, "0"); | |
3574 | return; | |
3575 | } | |
3576 | ||
3577 | is_local = IsLocalChannel(chan->chname); | |
3578 | ||
3579 | if (jbuf->jb_type == JOINBUF_TYPE_PART || | |
3580 | jbuf->jb_type == JOINBUF_TYPE_PARTALL) { | |
3581 | struct Membership *member = find_member_link(chan, jbuf->jb_source); | |
3582 | if (IsUserParting(member)) | |
3583 | return; | |
3584 | SetUserParting(member); | |
3585 | ||
3586 | /* Send notification to channel */ | |
3587 | if (!(flags & (CHFL_ZOMBIE | CHFL_DELAYED))) | |
3588 | sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_PART, chan, NULL, 0, | |
d8e74551 | 3589 | ((flags & CHFL_BANNED) || ((chan->mode.mode & MODE_NOQUITPARTS) |
3590 | && !IsChannelService(member->user)) || !jbuf->jb_comment) ? | |
3591 | "%H" : "%H :%s", chan, jbuf->jb_comment); | |
189935b1 | 3592 | else if (MyUser(jbuf->jb_source)) |
3593 | sendcmdto_one(jbuf->jb_source, CMD_PART, jbuf->jb_source, | |
d8e74551 | 3594 | ((flags & CHFL_BANNED) || (chan->mode.mode & MODE_NOQUITPARTS) |
3595 | || !jbuf->jb_comment) ? | |
189935b1 | 3596 | ":%H" : "%H :%s", chan, jbuf->jb_comment); |
3597 | /* XXX: Shouldn't we send a PART here anyway? */ | |
3598 | /* to users on the channel? Why? From their POV, the user isn't on | |
3599 | * the channel anymore anyway. We don't send to servers until below, | |
3600 | * when we gang all the channel parts together. Note that this is | |
3601 | * exactly the same logic, albeit somewhat more concise, as was in | |
3602 | * the original m_part.c */ | |
3603 | ||
3604 | if (jbuf->jb_type == JOINBUF_TYPE_PARTALL || | |
3605 | is_local) /* got to remove user here */ | |
3606 | remove_user_from_channel(jbuf->jb_source, chan); | |
3607 | } else { | |
3608 | int oplevel = !chan->mode.apass[0] ? MAXOPLEVEL | |
3609 | : (flags & CHFL_CHANNEL_MANAGER) ? 0 | |
3610 | : 1; | |
3611 | /* Add user to channel */ | |
3612 | if ((chan->mode.mode & MODE_DELJOINS) && !(flags & CHFL_VOICED_OR_OPPED)) | |
3613 | add_user_to_channel(chan, jbuf->jb_source, flags | CHFL_DELAYED, oplevel); | |
3614 | else | |
3615 | add_user_to_channel(chan, jbuf->jb_source, flags, oplevel); | |
3616 | ||
052b069e | 3617 | /* send JOIN notification to all servers (CREATE is sent later). */ |
189935b1 | 3618 | if (jbuf->jb_type != JOINBUF_TYPE_CREATE && !is_local) |
052b069e | 3619 | sendcmdto_serv_butone(jbuf->jb_source, CMD_JOIN, jbuf->jb_connect, |
3620 | "%H %Tu", chan, chan->creationtime); | |
189935b1 | 3621 | |
3622 | if (!((chan->mode.mode & MODE_DELJOINS) && !(flags & CHFL_VOICED_OR_OPPED))) { | |
3623 | /* Send the notification to the channel */ | |
3624 | sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_JOIN, chan, NULL, 0, "%H", chan); | |
3625 | ||
3626 | /* send an op, too, if needed */ | |
3627 | if (flags & CHFL_CHANOP && (oplevel < MAXOPLEVEL || !MyUser(jbuf->jb_source))) | |
3628 | sendcmdto_channel_butserv_butone((chan->mode.apass[0] ? &his : jbuf->jb_source), | |
3629 | CMD_MODE, chan, NULL, 0, "%H +o %C", | |
3630 | chan, jbuf->jb_source); | |
3631 | } else if (MyUser(jbuf->jb_source)) | |
3632 | sendcmdto_one(jbuf->jb_source, CMD_JOIN, jbuf->jb_source, ":%H", chan); | |
3633 | } | |
3634 | ||
3635 | if (jbuf->jb_type == JOINBUF_TYPE_PARTALL || | |
3636 | jbuf->jb_type == JOINBUF_TYPE_JOIN || is_local) | |
3637 | return; /* don't send to remote */ | |
3638 | ||
3639 | /* figure out if channel name will cause buffer to be overflowed */ | |
3640 | len = chan ? strlen(chan->chname) + 1 : 2; | |
3641 | if (jbuf->jb_strlen + len > BUFSIZE) | |
3642 | joinbuf_flush(jbuf); | |
3643 | ||
3644 | /* add channel to list of channels to send and update counts */ | |
3645 | jbuf->jb_channels[jbuf->jb_count++] = chan; | |
3646 | jbuf->jb_strlen += len; | |
3647 | ||
3648 | /* if we've used up all slots, flush */ | |
3649 | if (jbuf->jb_count >= MAXJOINARGS) | |
3650 | joinbuf_flush(jbuf); | |
3651 | } | |
3652 | ||
3653 | /* | |
3654 | * Flush the channel list to remote servers | |
3655 | */ | |
3656 | int | |
3657 | joinbuf_flush(struct JoinBuf *jbuf) | |
3658 | { | |
3659 | char chanlist[BUFSIZE]; | |
3660 | int chanlist_i = 0; | |
3661 | int i; | |
3662 | ||
3663 | if (!jbuf->jb_count || jbuf->jb_type == JOINBUF_TYPE_PARTALL || | |
3664 | jbuf->jb_type == JOINBUF_TYPE_JOIN) | |
3665 | return 0; /* no joins to process */ | |
3666 | ||
3667 | for (i = 0; i < jbuf->jb_count; i++) { /* build channel list */ | |
3668 | build_string(chanlist, &chanlist_i, | |
3669 | jbuf->jb_channels[i] ? jbuf->jb_channels[i]->chname : "0", 0, | |
3670 | i == 0 ? '\0' : ','); | |
3671 | if (JOINBUF_TYPE_PART == jbuf->jb_type) | |
3672 | /* Remove user from channel */ | |
3673 | remove_user_from_channel(jbuf->jb_source, jbuf->jb_channels[i]); | |
3674 | ||
3675 | jbuf->jb_channels[i] = 0; /* mark slot empty */ | |
3676 | } | |
3677 | ||
3678 | jbuf->jb_count = 0; /* reset base counters */ | |
3679 | jbuf->jb_strlen = ((jbuf->jb_type == JOINBUF_TYPE_PART ? | |
3680 | STARTJOINLEN : STARTCREATELEN) + | |
3681 | (jbuf->jb_comment ? strlen(jbuf->jb_comment) + 2 : 0)); | |
3682 | ||
3683 | /* and send the appropriate command */ | |
3684 | switch (jbuf->jb_type) { | |
3685 | case JOINBUF_TYPE_CREATE: | |
3686 | sendcmdto_serv_butone(jbuf->jb_source, CMD_CREATE, jbuf->jb_connect, | |
3687 | "%s %Tu", chanlist, jbuf->jb_create); | |
d8e74551 | 3688 | if (MyUser(jbuf->jb_source) && (feature_bool(FEAT_AUTOCHANMODES) && |
3689 | feature_str(FEAT_AUTOCHANMODES_LIST) && (strlen(feature_str(FEAT_AUTOCHANMODES_LIST)) > 0))) { | |
3690 | char *name; | |
3691 | char *p = 0; | |
3692 | for (name = ircd_strtok(&p, chanlist, ","); name; name = ircd_strtok(&p, 0, ",")) { | |
3693 | sendcmdto_serv_butone(jbuf->jb_source, CMD_MODE, jbuf->jb_connect, | |
3694 | "%s %s%s", name, "+", feature_str(FEAT_AUTOCHANMODES_LIST)); | |
3695 | } | |
3696 | } | |
189935b1 | 3697 | break; |
3698 | ||
3699 | case JOINBUF_TYPE_PART: | |
3700 | sendcmdto_serv_butone(jbuf->jb_source, CMD_PART, jbuf->jb_connect, | |
3701 | jbuf->jb_comment ? "%s :%s" : "%s", chanlist, | |
3702 | jbuf->jb_comment); | |
3703 | break; | |
3704 | } | |
3705 | ||
3706 | return 0; | |
3707 | } | |
3708 | ||
3709 | /* Returns TRUE (1) if client is invited, FALSE (0) if not */ | |
3710 | int IsInvited(struct Client* cptr, const void* chptr) | |
3711 | { | |
3712 | struct SLink *lp; | |
3713 | ||
3714 | for (lp = (cli_user(cptr))->invited; lp; lp = lp->next) | |
3715 | if (lp->value.chptr == chptr) | |
3716 | return 1; | |
3717 | return 0; | |
3718 | } | |
3719 | ||
3720 | /* RevealDelayedJoin: sends a join for a hidden user */ | |
3721 | ||
3722 | void RevealDelayedJoin(struct Membership *member) | |
3723 | { | |
3724 | ClearDelayedJoin(member); | |
3725 | sendcmdto_channel_butserv_butone(member->user, CMD_JOIN, member->channel, member->user, 0, ":%H", | |
3726 | member->channel); | |
3727 | CheckDelayedJoins(member->channel); | |
3728 | } | |
3729 | ||
3730 | /* CheckDelayedJoins: checks and clear +d if necessary */ | |
3731 | ||
3732 | void CheckDelayedJoins(struct Channel *chan) | |
3733 | { | |
4d6475c6 | 3734 | if ((chan->mode.mode & MODE_WASDELJOINS) && !find_delayed_joins(chan)) { |
3735 | chan->mode.mode &= ~MODE_WASDELJOINS; | |
3736 | sendcmdto_channel_butserv_butone(&his, CMD_MODE, chan, NULL, 0, | |
3737 | "%H -d", chan); | |
189935b1 | 3738 | } |
3739 | } |