1 |
/** |
2 |
* main.C: This file contains the main() routine. |
3 |
* |
4 |
* Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team |
5 |
* Rights to this code are as documented in COPYING. |
6 |
* |
7 |
* |
8 |
* Portions of this file were derived from sources bearing the following license: |
9 |
* Copyright © 2005 Atheme Development Group |
10 |
* Rights to this code are documented in doc/pod/license.pod. |
11 |
* |
12 |
* $Id: main.C,v 1.8 2007-09-16 18:54:43 pippijn Exp $ |
13 |
*/ |
14 |
|
15 |
#include <boost/foreach.hpp> |
16 |
|
17 |
#include "atheme.h" |
18 |
#include <util/numeric.h> |
19 |
#include <libermyth.h> |
20 |
#include "confparse.h" |
21 |
#include "servers.h" |
22 |
#include <ermyth/module.h> |
23 |
#include <account/myuser.h> |
24 |
#include <account/chanacs.h> |
25 |
#include <account/mychan.h> |
26 |
|
27 |
static char const rcsid[] = "$Id: main.C,v 1.8 2007-09-16 18:54:43 pippijn Exp $"; |
28 |
|
29 |
REGISTER_MODULE ("chanserv/main", false, "The Ermyth Team <http://ermyth.xinutec.org>"); |
30 |
|
31 |
cmdvec cs_cmdtree; |
32 |
helpvec cs_helptree; |
33 |
|
34 |
E list_t mychan; |
35 |
|
36 |
static void |
37 |
join_registered (bool all) |
38 |
{ |
39 |
mychan_t *mc; |
40 |
|
41 |
foreach (mychan_t::pair_type &mp, mychan_t::map) |
42 |
{ |
43 |
mc = mp.second; |
44 |
if (!(mc->flags & MC_GUARD)) |
45 |
continue; |
46 |
|
47 |
if (all) |
48 |
{ |
49 |
join (mc->name, chansvs.nick); |
50 |
continue; |
51 |
} |
52 |
else if (mc->chan != NULL && mc->chan->members.count != 0) |
53 |
{ |
54 |
join (mc->name, chansvs.nick); |
55 |
continue; |
56 |
} |
57 |
} |
58 |
} |
59 |
|
60 |
/* main services client routine */ |
61 |
static void |
62 |
chanserv (sourceinfo_t *si, int parc, char *parv[]) |
63 |
{ |
64 |
mychan_t *mc = NULL; |
65 |
char orig[BUFSIZE]; |
66 |
char newargs[BUFSIZE]; |
67 |
char *cmd; |
68 |
char *args; |
69 |
|
70 |
/* this should never happen */ |
71 |
if (parv[parc - 2][0] == '&') |
72 |
{ |
73 |
slog (LG_ERROR, "services(): got parv with local channel: %s", parv[0]); |
74 |
return; |
75 |
} |
76 |
|
77 |
/* is this a fantasy command? */ |
78 |
if (parv[parc - 2][0] == '#') |
79 |
{ |
80 |
metadata *md; |
81 |
|
82 |
if (chansvs.fantasy == false) |
83 |
{ |
84 |
/* *all* fantasy disabled */ |
85 |
return; |
86 |
} |
87 |
|
88 |
mc = mychan_t::find (parv[parc - 2]); |
89 |
if (!mc) |
90 |
{ |
91 |
/* unregistered, NFI how we got this message, but let's leave it alone! */ |
92 |
return; |
93 |
} |
94 |
|
95 |
md = mc->find_metadata ("disable_fantasy"); |
96 |
if (md) |
97 |
{ |
98 |
/* fantasy disabled on this channel. don't message them, just bail. */ |
99 |
return; |
100 |
} |
101 |
} |
102 |
|
103 |
/* make a copy of the original for debugging */ |
104 |
strlcpy (orig, parv[parc - 1], BUFSIZE); |
105 |
|
106 |
/* lets go through this to get the command */ |
107 |
cmd = strtok (parv[parc - 1], " "); |
108 |
|
109 |
if (!cmd) |
110 |
return; |
111 |
if (*cmd == '\001') |
112 |
{ |
113 |
handle_ctcp_common (si, cmd, strtok (NULL, "")); |
114 |
return; |
115 |
} |
116 |
|
117 |
/* take the command through the hash table */ |
118 |
if (mc == NULL) |
119 |
command_exec_split (si->service, si, cmd, strtok (NULL, ""), cs_cmdtree); |
120 |
else |
121 |
{ |
122 |
if (strlen (cmd) > 2 && (strchr (chansvs.trigger, *cmd) != NULL && isalpha (*++cmd))) |
123 |
{ |
124 |
/* XXX not really nice to look up the command twice |
125 |
* -- jilles */ |
126 |
if (cs_cmdtree.find (cmd) == NULL) |
127 |
return; |
128 |
if (floodcheck (si->su, si->service->me)) |
129 |
return; |
130 |
/* construct <channel> <args> */ |
131 |
strlcpy (newargs, parv[parc - 2], sizeof newargs); |
132 |
args = strtok (NULL, ""); |
133 |
if (args != NULL) |
134 |
{ |
135 |
strlcat (newargs, " ", sizeof newargs); |
136 |
strlcat (newargs, args, sizeof newargs); |
137 |
} |
138 |
/* let the command know it's called as fantasy cmd */ |
139 |
si->c = mc->chan; |
140 |
/* fantasy commands are always verbose |
141 |
* (a little ugly but this way we can !set verbose) |
142 |
*/ |
143 |
mc->flags |= MC_FORCEVERBOSE; |
144 |
command_exec_split (si->service, si, cmd, newargs, cs_cmdtree); |
145 |
mc->flags &= ~MC_FORCEVERBOSE; |
146 |
} |
147 |
else if (!strncasecmp (cmd, chansvs.nick, strlen (chansvs.nick)) && (cmd = strtok (NULL, "")) != NULL) |
148 |
{ |
149 |
char *pptr; |
150 |
|
151 |
strlcpy (newargs, parv[parc - 2], sizeof newargs); |
152 |
if ((pptr = strchr (cmd, ' ')) != NULL) |
153 |
{ |
154 |
*pptr = '\0'; |
155 |
|
156 |
strlcat (newargs, " ", sizeof newargs); |
157 |
strlcat (newargs, ++pptr, sizeof newargs); |
158 |
} |
159 |
|
160 |
if (cs_cmdtree.find (cmd) == NULL) |
161 |
return; |
162 |
if (floodcheck (si->su, si->service->me)) |
163 |
return; |
164 |
|
165 |
si->c = mc->chan; |
166 |
|
167 |
/* fantasy commands are always verbose |
168 |
* (a little ugly but this way we can !set verbose) |
169 |
*/ |
170 |
mc->flags |= MC_FORCEVERBOSE; |
171 |
command_exec_split (si->service, si, cmd, newargs, cs_cmdtree); |
172 |
mc->flags &= ~MC_FORCEVERBOSE; |
173 |
} |
174 |
} |
175 |
} |
176 |
|
177 |
static bool |
178 |
cs_join (chanuser_t *cu) |
179 |
{ |
180 |
user_t *u; |
181 |
channel_t *chan; |
182 |
mychan_t *mc; |
183 |
unsigned int flags; |
184 |
bool noop; |
185 |
bool secure; |
186 |
metadata *md; |
187 |
chanacs_t *ca2; |
188 |
char akickreason[120] = "User is banned from this channel", *p; |
189 |
|
190 |
if (cu == NULL || is_internal_client (cu->user)) |
191 |
return true; |
192 |
u = cu->user; |
193 |
chan = cu->chan; |
194 |
|
195 |
/* first check if this is a registered channel at all */ |
196 |
mc = mychan_t::find (chan->name); |
197 |
if (mc == NULL) |
198 |
return true; |
199 |
|
200 |
if (chan->nummembers == 1 && mc->flags & MC_GUARD) |
201 |
join (chan->name, chansvs.nick); |
202 |
|
203 |
flags = chanacs_user_flags (mc, u); |
204 |
noop = mc->flags & MC_NOOP || (u->myuser != NULL && u->myuser->flags & MU_NOOP); |
205 |
/* attempt to deop people recreating channels, if the more |
206 |
* sophisticated mechanism is disabled */ |
207 |
secure = mc->flags & MC_SECURE || (!chansvs.changets && chan->nummembers == 1 && chan->ts > NOW - 300); |
208 |
|
209 |
/* auto stuff */ |
210 |
if ((mc->flags & MC_STAFFONLY) && !has_priv_user (u, PRIV_JOIN_STAFFONLY)) |
211 |
{ |
212 |
/* Stay on channel if this would empty it -- jilles */ |
213 |
if (chan->nummembers <= (mc->flags & MC_GUARD ? 2 : 1)) |
214 |
{ |
215 |
mc->flags |= MC_INHABIT; |
216 |
if (!(mc->flags & MC_GUARD)) |
217 |
join (chan->name, chansvs.nick); |
218 |
} |
219 |
ban (chansvs.me->me, chan, u); |
220 |
remove_ban_exceptions (chansvs.me->me, chan, u); |
221 |
phandler->kick (chansvs.nick, chan->name, u->nick, "You are not authorized to be on this channel"); |
222 |
return false; |
223 |
} |
224 |
|
225 |
if (flags & CA_AKICK && !(flags & CA_REMOVE)) |
226 |
{ |
227 |
/* Stay on channel if this would empty it -- jilles */ |
228 |
if (chan->nummembers <= (mc->flags & MC_GUARD ? 2 : 1)) |
229 |
{ |
230 |
mc->flags |= MC_INHABIT; |
231 |
if (!(mc->flags & MC_GUARD)) |
232 |
join (chan->name, chansvs.nick); |
233 |
} |
234 |
/* use a user-given ban mask if possible -- jilles */ |
235 |
ca2 = chanacs_find_host_by_user (mc, u, CA_AKICK); |
236 |
if (ca2 != NULL) |
237 |
{ |
238 |
if (chanban_find (chan, ca2->host, 'b') == NULL) |
239 |
{ |
240 |
char str[512]; |
241 |
|
242 |
chanban_add (chan, ca2->host, 'b'); |
243 |
snprintf (str, sizeof str, "+b %s", ca2->host); |
244 |
/* ban immediately */ |
245 |
phandler->mode_sts (chansvs.nick, chan, str); |
246 |
} |
247 |
} |
248 |
else |
249 |
{ |
250 |
/* XXX this could be done more efficiently */ |
251 |
ca2 = chanacs_find (mc, u->myuser, CA_AKICK); |
252 |
ban (chansvs.me->me, chan, u); |
253 |
} |
254 |
remove_ban_exceptions (chansvs.me->me, chan, u); |
255 |
if (ca2 != NULL) |
256 |
{ |
257 |
md = ca2->find_metadata ("reason"); |
258 |
if (md != NULL && *md->value != '|') |
259 |
{ |
260 |
snprintf (akickreason, sizeof akickreason, "Banned: %s", md->value); |
261 |
p = strchr (akickreason, '|'); |
262 |
if (p != NULL) |
263 |
*p = '\0'; |
264 |
else |
265 |
p = akickreason + strlen (akickreason); |
266 |
/* strip trailing spaces, so as not to |
267 |
* disclose the existence of an oper reason */ |
268 |
p--; |
269 |
while (p > akickreason && *p == ' ') |
270 |
p--; |
271 |
p[1] = '\0'; |
272 |
} |
273 |
} |
274 |
phandler->kick (chansvs.nick, chan->name, u->nick, akickreason); |
275 |
return false; |
276 |
} |
277 |
|
278 |
/* Kick out users who may be recreating channels mlocked +i. |
279 |
* Users with +i flag are allowed to join, as are users matching |
280 |
* an invite exception (the latter only works if the channel already |
281 |
* exists because members are sent before invite exceptions). |
282 |
* Because most networks do not use kick_on_split_riding or |
283 |
* no_create_on_split, do not trust users coming back from splits. |
284 |
* Unfortunately, this kicks users who have been invited by a channel |
285 |
* operator, after a split or services restart. |
286 |
*/ |
287 |
if (mc->mlock_on & CMODE_INVITE && !(flags & CA_INVITE) && (!(u->server->flags & SF_EOB) || (chan->nummembers <= 2 && (chan->nummembers <= 1 || chanuser_find (chan, chansvs.me->me)))) && (!ircd->invex_mchar || !phandler->next_matching_ban (chan, u, ircd->invex_mchar, chan->bans.head))) |
288 |
{ |
289 |
if (chan->nummembers <= (mc->flags & MC_GUARD ? 2 : 1)) |
290 |
{ |
291 |
mc->flags |= MC_INHABIT; |
292 |
if (!(mc->flags & MC_GUARD)) |
293 |
join (chan->name, chansvs.nick); |
294 |
} |
295 |
if (!(chan->modes & CMODE_INVITE)) |
296 |
check_modes (mc, true); |
297 |
modestack_flush_channel (chan); |
298 |
phandler->kick (chansvs.nick, chan->name, u->nick, "Invite only channel"); |
299 |
return false; |
300 |
} |
301 |
|
302 |
/* A user joined and was not kicked; we do not need |
303 |
* to stay on the channel artificially. */ |
304 |
if (mc->flags & MC_INHABIT) |
305 |
{ |
306 |
mc->flags &= ~MC_INHABIT; |
307 |
if (!(mc->flags & MC_GUARD) && (!config_options.chan || irccasecmp (chan->name, config_options.chan)) && chanuser_find (chan, chansvs.me->me)) |
308 |
part (chan->name, chansvs.nick); |
309 |
} |
310 |
|
311 |
if (ircd->uses_owner) |
312 |
{ |
313 |
if (flags & CA_FOUNDER) |
314 |
{ |
315 |
if (flags & CA_AUTOOP && !(noop || cu->modes & ircd->owner_mode)) |
316 |
{ |
317 |
modestack_mode_param (chansvs.nick, chan, MTYPE_ADD, ircd->owner_mchar[1], CLIENT_NAME (u)); |
318 |
cu->modes |= ircd->owner_mode; |
319 |
} |
320 |
} |
321 |
else if (secure && (cu->modes & ircd->owner_mode)) |
322 |
{ |
323 |
modestack_mode_param (chansvs.nick, chan, MTYPE_DEL, ircd->owner_mchar[1], CLIENT_NAME (u)); |
324 |
cu->modes &= ~ircd->owner_mode; |
325 |
} |
326 |
} |
327 |
|
328 |
/* XXX still uses should_protect() */ |
329 |
if (ircd->uses_protect) |
330 |
{ |
331 |
if (u->myuser != NULL && u->myuser->should_protect (mc)) |
332 |
{ |
333 |
if (flags & CA_AUTOOP && !(noop || cu->modes & ircd->protect_mode)) |
334 |
{ |
335 |
modestack_mode_param (chansvs.nick, chan, MTYPE_ADD, ircd->protect_mchar[1], CLIENT_NAME (u)); |
336 |
cu->modes |= ircd->protect_mode; |
337 |
} |
338 |
} |
339 |
else if (secure && (cu->modes & ircd->protect_mode)) |
340 |
{ |
341 |
modestack_mode_param (chansvs.nick, chan, MTYPE_DEL, ircd->protect_mchar[1], CLIENT_NAME (u)); |
342 |
cu->modes &= ~ircd->protect_mode; |
343 |
} |
344 |
} |
345 |
|
346 |
if (flags & CA_AUTOOP) |
347 |
{ |
348 |
if (!(noop || cu->modes & CMODE_OP)) |
349 |
{ |
350 |
modestack_mode_param (chansvs.nick, chan, MTYPE_ADD, 'o', CLIENT_NAME (u)); |
351 |
cu->modes |= CMODE_OP; |
352 |
} |
353 |
} |
354 |
else if (secure && (cu->modes & CMODE_OP) && !(flags & CA_OP)) |
355 |
{ |
356 |
modestack_mode_param (chansvs.nick, chan, MTYPE_DEL, 'o', CLIENT_NAME (u)); |
357 |
cu->modes &= ~CMODE_OP; |
358 |
} |
359 |
|
360 |
if (ircd->uses_halfops) |
361 |
{ |
362 |
if (flags & CA_AUTOHALFOP) |
363 |
{ |
364 |
if (!(noop || cu->modes & (CMODE_OP | ircd->halfops_mode))) |
365 |
{ |
366 |
modestack_mode_param (chansvs.nick, chan, MTYPE_ADD, 'h', CLIENT_NAME (u)); |
367 |
cu->modes |= ircd->halfops_mode; |
368 |
} |
369 |
} |
370 |
else if (secure && (cu->modes & ircd->halfops_mode) && !(flags & CA_HALFOP)) |
371 |
{ |
372 |
modestack_mode_param (chansvs.nick, chan, MTYPE_DEL, 'h', CLIENT_NAME (u)); |
373 |
cu->modes &= ~ircd->halfops_mode; |
374 |
} |
375 |
} |
376 |
|
377 |
if (flags & CA_AUTOVOICE) |
378 |
{ |
379 |
if (!(noop || cu->modes & (CMODE_OP | ircd->halfops_mode | CMODE_VOICE))) |
380 |
{ |
381 |
modestack_mode_param (chansvs.nick, chan, MTYPE_ADD, 'v', CLIENT_NAME (u)); |
382 |
cu->modes |= CMODE_VOICE; |
383 |
} |
384 |
} |
385 |
|
386 |
if (u->server->flags & SF_EOB && (md = mc->find_metadata ("private:entrymsg"))) |
387 |
notice (chansvs.nick, cu->user->nick, "[%s] %s", mc->name, md->value); |
388 |
|
389 |
if (u->server->flags & SF_EOB && (md = mc->find_metadata ("url"))) |
390 |
phandler->numeric_sts (me.name, 328, cu->user->nick, "%s :%s", mc->name, md->value); |
391 |
|
392 |
if (flags & CA_USEDUPDATE) |
393 |
mc->used = NOW; |
394 |
|
395 |
return true; |
396 |
} |
397 |
|
398 |
static void |
399 |
cs_part (chanuser_t *cu) |
400 |
{ |
401 |
mychan_t *mc; |
402 |
|
403 |
if (cu == NULL) |
404 |
return; |
405 |
mc = mychan_t::find (cu->chan->name); |
406 |
if (mc == NULL) |
407 |
return; |
408 |
if (NOW - mc->used >= 3600) |
409 |
if (chanacs_user_flags (mc, cu->user) & CA_USEDUPDATE) |
410 |
mc->used = NOW; |
411 |
/* |
412 |
* When channel_part is fired, we haven't yet removed the |
413 |
* user from the room. So, the channel will have two members |
414 |
* if ChanServ is joining channels: the triggering user and |
415 |
* itself. |
416 |
* |
417 |
* Do not part if we're enforcing an akick/close in an otherwise |
418 |
* empty channel (MC_INHABIT). -- jilles |
419 |
*/ |
420 |
if ((mc->flags & MC_GUARD) && config_options.leave_chans && !(mc->flags & MC_INHABIT) && (cu->chan->nummembers <= 2) && !is_internal_client (cu->user)) |
421 |
part (cu->chan->name, chansvs.nick); |
422 |
} |
423 |
|
424 |
static void |
425 |
cs_register (mychan_t *mc, sourceinfo_t *si) |
426 |
{ |
427 |
if (mc->chan) |
428 |
{ |
429 |
if (mc->flags & MC_GUARD) |
430 |
join (mc->name, chansvs.nick); |
431 |
|
432 |
check_modes (mc, true); |
433 |
} |
434 |
} |
435 |
|
436 |
/* Called on every set of a topic, after updating our internal structures */ |
437 |
static void |
438 |
cs_keeptopic_topicset (channel_t *c) |
439 |
{ |
440 |
mychan_t *mc; |
441 |
metadata *md; |
442 |
|
443 |
mc = mychan_t::find (c->name); |
444 |
|
445 |
if (mc == NULL) |
446 |
return; |
447 |
|
448 |
md = mc->find_metadata ("private:topic:text"); |
449 |
if (md != NULL) |
450 |
{ |
451 |
if (c->topic != NULL && !strcmp (md->value, c->topic)) |
452 |
return; |
453 |
mc->del_metadata ("private:topic:text"); |
454 |
} |
455 |
|
456 |
if (mc->find_metadata ("private:topic:setter")) |
457 |
mc->del_metadata ("private:topic:setter"); |
458 |
|
459 |
if (mc->find_metadata ("private:topic:ts")) |
460 |
mc->del_metadata ("private:topic:ts"); |
461 |
|
462 |
if (c->topic && c->topic_setter) |
463 |
{ |
464 |
slog (LG_DEBUG, "KeepTopic: topic set for %s by %s: %s", c->name, c->topic_setter, c->topic); |
465 |
mc->add_metadata ("private:topic:setter", c->topic_setter); |
466 |
mc->add_metadata ("private:topic:text", c->topic); |
467 |
mc->add_metadata ("private:topic:ts", itoa (c->topicts)); |
468 |
} |
469 |
else |
470 |
slog (LG_DEBUG, "KeepTopic: topic cleared for %s", c->name); |
471 |
} |
472 |
|
473 |
/* Called when a topic change is received from the network, before altering |
474 |
* our internal structures */ |
475 |
static bool |
476 |
cs_topiccheck (channel_t *c, user_t *u, server_t *s, char const * const topic, time_t ts, char const * const setter) |
477 |
{ |
478 |
mychan_t *mc = mychan_t::find (c->name); |
479 |
unsigned int accessfl = 0; |
480 |
bool approved = true; |
481 |
|
482 |
if (mc == NULL) |
483 |
return false; |
484 |
|
485 |
if ((mc->flags & (MC_KEEPTOPIC | MC_TOPICLOCK)) == (MC_KEEPTOPIC | MC_TOPICLOCK)) |
486 |
{ |
487 |
if (u == NULL || !((accessfl = chanacs_user_flags (mc, u)) & CA_TOPIC)) |
488 |
{ |
489 |
/* topic burst or unauthorized user, revert it */ |
490 |
approved = false; |
491 |
slog (LG_DEBUG, "cs_topiccheck(): reverting topic change on channel %s by %s", c->name, u != NULL ? u->nick : "<server>"); |
492 |
|
493 |
if (u != NULL && !(mc->mlock_off & CMODE_TOPIC)) |
494 |
{ |
495 |
/* they do not have access to be opped either, |
496 |
* deop them and set +t */ |
497 |
/* note: channel_mode() takes nicks, not UIDs |
498 |
* when used with a non-NULL source */ |
499 |
if (ircd->uses_halfops && !(accessfl & (CA_OP | CA_AUTOOP | CA_HALFOP | CA_AUTOHALFOP))) |
500 |
channel_mode_va (chansvs.me->me, c, 3, "+t-oh", u->nick, u->nick); |
501 |
else if (!ircd->uses_halfops && !(accessfl & (CA_OP | CA_AUTOOP))) |
502 |
channel_mode_va (chansvs.me->me, c, 2, "+t-o", u->nick); |
503 |
} |
504 |
} |
505 |
} |
506 |
|
507 |
return approved; |
508 |
} |
509 |
|
510 |
/* Called on creation of a channel */ |
511 |
static void |
512 |
cs_newchan (channel_t *c) |
513 |
{ |
514 |
mychan_t *mc; |
515 |
chanuser_t *cu; |
516 |
metadata *md; |
517 |
char *setter; |
518 |
char *text; |
519 |
time_t channelts = 0; |
520 |
time_t topicts; |
521 |
char str[21]; |
522 |
|
523 |
if (!(mc = mychan_t::find (c->name))) |
524 |
return; |
525 |
|
526 |
/* schedule a mode lock check when we know the current modes |
527 |
* -- jilles */ |
528 |
mc->flags |= MC_MLOCK_CHECK; |
529 |
|
530 |
md = mc->find_metadata ("private:channelts"); |
531 |
if (md != NULL) |
532 |
channelts = atol (md->value); |
533 |
if (channelts == 0) |
534 |
channelts = mc->registered; |
535 |
|
536 |
if (chansvs.changets && c->ts > channelts && channelts > 0) |
537 |
{ |
538 |
/* Stop the splitrider -- jilles */ |
539 |
c->ts = channelts; |
540 |
clear_simple_modes (c); |
541 |
c->modes |= CMODE_NOEXT | CMODE_TOPIC; |
542 |
check_modes (mc, false); |
543 |
/* No ops to clear */ |
544 |
phandler->chan_lowerts (c, chansvs.me->me); |
545 |
cu = chanuser_add (c, CLIENT_NAME (chansvs.me->me)); |
546 |
cu->modes |= CMODE_OP; |
547 |
/* make sure it parts again sometime (empty SJOIN etc) */ |
548 |
mc->flags |= MC_INHABIT; |
549 |
} |
550 |
else if (c->ts != channelts) |
551 |
{ |
552 |
snprintf (str, sizeof str, "%lu", (unsigned long) c->ts); |
553 |
mc->add_metadata ("private:channelts", str); |
554 |
} |
555 |
else if (!(MC_TOPICLOCK & mc->flags) && LIST_LENGTH (&c->members) == 0) |
556 |
/* Same channel, let's assume ircd has kept topic. |
557 |
* However, if topiclock is enabled, we must change it back |
558 |
* regardless. |
559 |
* Also, if there is someone in this channel already, it is |
560 |
* being created by a service and we must restore. |
561 |
* -- jilles */ |
562 |
return; |
563 |
|
564 |
if (!(MC_KEEPTOPIC & mc->flags)) |
565 |
return; |
566 |
|
567 |
md = mc->find_metadata ("private:topic:setter"); |
568 |
if (md == NULL) |
569 |
return; |
570 |
setter = md->value; |
571 |
|
572 |
md = mc->find_metadata ("private:topic:text"); |
573 |
if (md == NULL) |
574 |
return; |
575 |
text = md->value; |
576 |
|
577 |
md = mc->find_metadata ("private:topic:ts"); |
578 |
if (md == NULL) |
579 |
return; |
580 |
topicts = atol (md->value); |
581 |
|
582 |
handle_topic (c, setter, topicts, text); |
583 |
phandler->topic_sts (c, setter, topicts, 0, text); |
584 |
} |
585 |
|
586 |
static void |
587 |
cs_tschange (channel_t *c) |
588 |
{ |
589 |
mychan_t *mc; |
590 |
char str[21]; |
591 |
|
592 |
if (!(mc = mychan_t::find (c->name))) |
593 |
return; |
594 |
|
595 |
/* store new TS */ |
596 |
snprintf (str, sizeof str, "%lu", (unsigned long) c->ts); |
597 |
mc->add_metadata ("private:channelts", str); |
598 |
|
599 |
/* schedule a mode lock check when we know the new modes |
600 |
* -- jilles */ |
601 |
mc->flags |= MC_MLOCK_CHECK; |
602 |
} |
603 |
|
604 |
static void |
605 |
cs_leave_empty (void *unused) |
606 |
{ |
607 |
mychan_t *mc; |
608 |
|
609 |
foreach (mychan_t::pair_type &mp, mychan_t::map) |
610 |
{ |
611 |
mc = mp.second; |
612 |
if (!(mc->flags & MC_INHABIT)) |
613 |
continue; |
614 |
mc->flags &= ~MC_INHABIT; |
615 |
if (mc->chan != NULL && (!config_options.chan || irccasecmp (mc->name, config_options.chan)) && (!(mc->flags & MC_GUARD) || (config_options.leave_chans && mc->chan->nummembers == 1) || mc->find_metadata ("private:close:closer")) && chanuser_find (mc->chan, chansvs.me->me)) |
616 |
{ |
617 |
slog (LG_DEBUG, "cs_leave_empty(): leaving %s", mc->chan->name); |
618 |
part (mc->chan->name, chansvs.nick); |
619 |
} |
620 |
} |
621 |
} |
622 |
|
623 |
static void |
624 |
on_config_ready (void) |
625 |
{ |
626 |
if (chansvs.me) |
627 |
del_service (chansvs.me); |
628 |
|
629 |
chansvs.me = add_service (chansvs.nick, chansvs.user, chansvs.host, chansvs.real, chanserv, cs_cmdtree); |
630 |
chansvs.disp = chansvs.me->disp; |
631 |
|
632 |
chansvs.me->set_chanmsg (true); |
633 |
|
634 |
if (me.connected) |
635 |
join_registered (!config_options.leave_chans); |
636 |
} |
637 |
|
638 |
bool |
639 |
_modinit (module *m) |
640 |
{ |
641 |
ConfTable::callback.ready.attach (on_config_ready); |
642 |
|
643 |
if (!cold_start) |
644 |
{ |
645 |
chansvs.me = add_service (chansvs.nick, chansvs.user, chansvs.host, chansvs.real, chanserv, cs_cmdtree); |
646 |
chansvs.disp = chansvs.me->disp; |
647 |
|
648 |
chansvs.me->set_chanmsg (true); |
649 |
|
650 |
if (me.connected) |
651 |
join_registered (!config_options.leave_chans); |
652 |
} |
653 |
|
654 |
channel_t::callback.add.attach (cs_newchan); |
655 |
chanuser_t::callback.join.attach (cs_join); |
656 |
chanuser_t::callback.part.attach (cs_part); |
657 |
mychan_t::callback.registered.attach (cs_register); |
658 |
channel_t::callback.topic.attach (cs_keeptopic_topicset); |
659 |
channel_t::callback.can_change_topic.attach (cs_topiccheck); |
660 |
channel_t::callback.tschange.attach (cs_tschange); |
661 |
event_add ("cs_leave_empty", cs_leave_empty, NULL, 300); |
662 |
|
663 |
return true; |
664 |
} |
665 |
|
666 |
void |
667 |
_moddeinit () |
668 |
{ |
669 |
if (chansvs.me) |
670 |
{ |
671 |
del_service (chansvs.me); |
672 |
chansvs.me = NULL; |
673 |
} |
674 |
|
675 |
channel_t::callback.add.detach (cs_newchan); |
676 |
chanuser_t::callback.join.detach (cs_join); |
677 |
chanuser_t::callback.part.detach (cs_part); |
678 |
mychan_t::callback.registered.detach (cs_register); |
679 |
channel_t::callback.topic.detach (cs_keeptopic_topicset); |
680 |
channel_t::callback.can_change_topic.detach (cs_topiccheck); |
681 |
channel_t::callback.tschange.detach (cs_tschange); |
682 |
event_delete (cs_leave_empty, NULL); |
683 |
|
684 |
ConfTable::callback.ready.detach (on_config_ready); |
685 |
} |