]> git.donarmstrong.com Git - infobot.git/blob - src/IRC/Irc.pl
- nick code is FUCKED. partial fix here.
[infobot.git] / src / IRC / Irc.pl
1 #
2 #    Irc.pl: IRC core stuff.
3 #    Author: dms
4 #   Version: 20000126
5 #      NOTE: Based on code by Kevin Lenzo & Patrick Cole  (c) 1997
6 #
7
8 use strict;
9 no strict 'refs';
10
11 use vars qw(%floodjoin %nuh %dcc %cache %channels %param %mask
12         %chanconf %orig %ircPort %ircstats %last %netsplit);
13 use vars qw($irc $nickserv $ident $conn $msgType $who $talkchannel
14         $addressed);
15 use vars qw($notcount $nottime $notsize $msgcount $msgtime $msgsize
16                 $pubcount $pubtime $pubsize);
17 use vars qw($b_blue $ob);
18 use vars qw(@joinchan @ircServers);
19
20 # static scalar variables.
21 $mask{ip}       = '(\d+)\.(\d+)\.(\d+)\.(\d+)';
22 $mask{host}     = '[\d\w\_\-\/]+\.[\.\d\w\_\-\/]+';
23 $mask{chan}     = '[\#\&]\S*|_default';
24 my $isnick1     = 'a-zA-Z\[\]\{\}\_\`\^\|\\\\';
25 my $isnick2     = '0-9\-';
26 $mask{nick}     = "[$isnick1]{1}[$isnick1$isnick2]*";
27 $mask{nuh}      = '\S*!\S*\@\S*';
28
29 $nickserv       = 0;
30
31 sub ircloop {
32     my $error   = 0;
33     my $lastrun = 0;
34
35 loop:;
36     while (my $host = shift @ircServers) {
37         # JUST IN CASE. irq was complaining about this.
38         if ($lastrun == time()) {
39             &DEBUG("ircloop: hrm... lastrun == time()");
40             $error++;
41             sleep 10;
42             next;
43         }
44
45         if (!defined $host) {
46             &DEBUG("ircloop: ircServers[x] = NULL.");
47             $lastrun = time();
48             next;
49         }
50         next unless (exists $ircPort{$host});
51
52         my $retval      = &irc($host, $ircPort{$host});
53         next unless (defined $retval and $retval == 0);
54         $error++;
55
56         if ($error % 3 == 0 and $error != 0) {
57             &status("IRC: Could not connect.");
58             &status("IRC: ");
59             next;
60         }
61
62         if ($error >= 3*2) {
63             &status("IRC: cannot connect to any IRC servers; stopping.");
64             &shutdown();
65             exit 1;
66         }
67     }
68
69     &status("IRC: ok, done one cycle of IRC servers; trying again.");
70
71     &loadIRCServers();
72     goto loop;
73 }
74
75 sub irc {
76     my ($server,$port) = @_;
77
78     my $iaddr = inet_aton($server);
79     my $paddr = sockaddr_in($port, $iaddr);
80     my $proto = getprotobyname('tcp');
81
82     select STDOUT;
83     &status("Connecting to port $port of server $server ...");
84
85     # host->ip.
86     if ($server =~ /\D$/) {
87         my $packed = scalar(gethostbyname($server));
88
89         if (!defined $packed) {
90             &status("  cannot resolve $server.");
91             return 0;
92         }
93
94         my $resolve = inet_ntoa($packed);
95         &status("  resolved to $resolve.");
96         ### warning in Sys/Hostname line 78???
97         ### caused inside Net::IRC?
98     }
99
100     $irc = new Net::IRC;
101
102     my %args = (
103                 Nick    => $param{'ircNick'},
104                 Server  => $server,
105                 Port    => $port,
106                 Ircname => $param{'ircName'},
107     );
108     $args{'LocalAddr'} = $param{'ircHost'} if ($param{'ircHost'});
109     $args{'Password'} = $param{'ircPasswd'} if ($param{'ircPasswd'});
110
111     $conn = $irc->newconn(%args);
112
113     if (!defined $conn) {
114         &ERROR("internal: perl IRC connection object does not exist.");
115         return 1;
116     }
117
118     &clearIRCVars();
119
120     # change internal timeout value for scheduler.
121     $irc->{_timeout}    = 10;   # how about 60?
122     # Net::IRC debugging.
123     $irc->{_debug}      = 1;
124
125     $ircstats{'Server'} = "$server:$port";
126
127     # handler stuff.
128         $conn->add_handler('caction',   \&on_action);
129         $conn->add_handler('cdcc',      \&on_dcc);
130         $conn->add_handler('cping',     \&on_ping);
131         $conn->add_handler('crping',    \&on_ping_reply);
132         $conn->add_handler('cversion',  \&on_version);
133         $conn->add_handler('crversion', \&on_crversion);
134         $conn->add_handler('dcc_open',  \&on_dcc_open);
135         $conn->add_handler('dcc_close', \&on_dcc_close);
136         $conn->add_handler('chat',      \&on_chat);
137         $conn->add_handler('msg',       \&on_msg);
138         $conn->add_handler('public',    \&on_public);
139         $conn->add_handler('join',      \&on_join);
140         $conn->add_handler('part',      \&on_part);
141         $conn->add_handler('topic',     \&on_topic);
142         $conn->add_handler('invite',    \&on_invite);
143         $conn->add_handler('kick',      \&on_kick);
144         $conn->add_handler('mode',      \&on_mode);
145         $conn->add_handler('nick',      \&on_nick);
146         $conn->add_handler('quit',      \&on_quit);
147         $conn->add_handler('notice',    \&on_notice);
148         $conn->add_handler('whoischannels', \&on_whoischannels);
149         $conn->add_handler('useronchannel', \&on_useronchannel);
150         $conn->add_handler('whois',     \&on_whois);
151         $conn->add_handler('other',     \&on_other);
152         $conn->add_global_handler('disconnect', \&on_disconnect);
153         $conn->add_global_handler([251,252,253,254,255], \&on_init);
154 ###     $conn->add_global_handler([251,252,253,254,255,302], \&on_init);
155         $conn->add_global_handler(303, \&on_ison); # notify.
156         $conn->add_global_handler(315, \&on_endofwho);
157         $conn->add_global_handler(422, \&on_endofwho); # nomotd.
158         $conn->add_global_handler(324, \&on_modeis);
159         $conn->add_global_handler(333, \&on_topicinfo);
160         $conn->add_global_handler(352, \&on_who);
161         $conn->add_global_handler(353, \&on_names);
162         $conn->add_global_handler(366, \&on_endofnames);
163         $conn->add_global_handler(376, \&on_endofmotd); # on_connect.
164         $conn->add_global_handler(433, \&on_nick_taken);
165         $conn->add_global_handler(439, \&on_targettoofast);
166         # for proper joinnextChan behaviour
167         $conn->add_global_handler(471, \&on_chanfull);
168         $conn->add_global_handler(473, \&on_inviteonly);
169         $conn->add_global_handler(474, \&on_banned);
170         $conn->add_global_handler(475, \&on_badchankey);
171         $conn->add_global_handler(443, \&on_useronchan);
172
173     # end of handler stuff.
174
175     $irc->start;
176 }
177
178 ######################################################################
179 ######## IRC ALIASES   IRC ALIASES   IRC ALIASES   IRC ALIASES #######
180 ######################################################################
181
182 sub rawout {
183     my ($buf) = @_;
184     $buf =~ s/\n//gi;
185
186     # slow down a bit if traffic is "high".
187     # need to take into account time of last message sent.
188     if ($last{buflen} > 256 and length($buf) > 256) {
189         sleep 1;
190     }
191
192     $conn->sl($buf) if (&whatInterface() =~ /IRC/);
193
194     $last{buflen} = length($buf);
195 }
196
197 sub say {
198     my ($msg) = @_;
199     if (!defined $msg) {
200         $msg ||= "NULL";
201         &WARN("say: msg == $msg.");
202         return;
203     }
204
205     &status("</$talkchannel> $msg");
206     if (&whatInterface() =~ /IRC/) {
207         $msg    = "zero" if ($msg =~ /^0+$/);
208         my $t   = time();
209
210         if ($t == $pubtime) {
211             $pubcount++;
212             $pubsize += length $msg;
213
214             my $i = &getChanConfDefault("sendPublicLimitLines", 3);
215             my $j = &getChanConfDefault("sendPublicLimitBytes", 1000);
216
217             if ( ($pubcount % $i) == 0 and $pubcount) {
218                 sleep 1;
219             } elsif ($pubsize > $j) {
220                 sleep 1;
221                 $pubsize -= $j;
222             }
223
224         } else {
225             $pubcount   = 0;
226             $pubtime    = $t;
227             $pubsize    = length $msg;
228         }
229
230         $conn->privmsg($talkchannel, $msg);
231     }
232 }
233
234 sub msg {
235     my ($nick, $msg) = @_;
236     if (!defined $nick) {
237         &ERROR("msg: nick == NULL.");
238         return;
239     }
240
241     if (!defined $msg) {
242         $msg ||= "NULL";
243         &WARN("msg: msg == $msg.");
244         return;
245     }
246
247     if ($msgType =~ /chat/i) {
248         # todo: warn that we're using msg() to do DCC CHAT?
249         &dccsay($nick, $msg);
250         # todo: make dccsay deal with flood protection?
251         return;
252     }
253
254     &status(">$nick< $msg");
255
256     if (&whatInterface() =~ /IRC/) {
257         my $t = time();
258
259         if ($t == $msgtime) {
260             $msgcount++;
261             $msgsize += length $msg;
262
263             my $i = &getChanConfDefault("sendPrivateLimitLines", 3);
264             my $j = &getChanConfDefault("sendPrivateLimitBytes", 1000);
265             if ( ($msgcount % $i) == 0 and $msgcount) {
266                 sleep 1;
267             } elsif ($msgsize > $j) {
268                 sleep 1;
269                 $msgsize -= $j;
270             }
271
272         } else {
273             $msgcount   = 0;
274             $msgtime    = $t;
275             $msgsize    = length $msg;
276         }
277
278         $conn->privmsg($nick, $msg);
279     }
280 }
281
282 # Usage: &action(nick || chan, txt);
283 sub action {
284     my ($target, $txt) = @_;
285     if (!defined $txt) {
286         &WARN("action: txt == NULL.");
287         return;
288     }
289
290     if (length $txt > 480) {
291         &status("action: txt too long; truncating.");
292         chop($txt) while (length $txt > 480);
293     }
294
295     &status("* $ident/$target $txt");
296     $conn->me($target, $txt);
297 }
298
299 # Usage: &notice(nick || chan, txt);
300 sub notice {
301     my ($target, $txt) = @_;
302     if (!defined $txt) {
303         &WARN("notice: txt == NULL.");
304         return;
305     }
306
307     &status("-$target- $txt");
308
309     my $t       = time();
310
311     if ($t == $nottime) {
312         $notcount++;
313         $notsize += length $txt;
314
315         my $i = &getChanConfDefault("sendNoticeLimitLines", 3);
316         my $j = &getChanConfDefault("sendNoticeLimitBytes", 1000);
317
318         if ( ($notcount % $i) == 0 and $notcount) {
319             sleep 1;
320         } elsif ($notsize > $j) {
321             sleep 1;
322             $notsize -= $j;
323         }
324
325     } else {
326         $notcount       = 0;
327         $nottime        = $t;
328         $notsize        = length $txt;
329     }
330
331     $conn->notice($target, $txt);
332 }
333
334 sub DCCBroadcast {
335     my ($txt,$flag) = @_;
336
337     ### FIXME: flag not supported yet.
338
339     foreach (keys %{ $dcc{'CHAT'} }) {
340         $conn->privmsg($dcc{'CHAT'}{$_}, $txt);
341     }
342 }
343
344 ##########
345 ### perform commands.
346 ###
347
348 # Usage: &performReply($reply);
349 sub performReply {
350     my ($reply) = @_;
351     $reply =~ /([\.\?\s]+)$/;
352
353     &checkMsgType($reply);
354
355     if ($msgType eq 'public') {
356         if (rand() < 0.5 or $reply =~ /[\.\?]$/) {
357             $reply = "$orig{who}: ".$reply;
358         } else {
359             $reply = "$reply, ".$orig{who};
360         }
361         &say($reply);
362     } elsif ($msgType eq 'private') {
363         if (rand() < 0.5) {
364             $reply = $reply;
365         } else {
366             $reply = "$reply, ".$orig{who};
367         }
368         &msg($who, $reply);
369     } elsif ($msgType eq 'chat') {
370         if (!exists $dcc{'CHAT'}{$who}) {
371             &VERB("pSR: dcc{'CHAT'}{$who} does not exist.",2);
372             return;
373         }
374         $conn->privmsg($dcc{'CHAT'}{$who}, $reply);
375     } else {
376         &ERROR("PR: msgType invalid? ($msgType).");
377     }
378 }
379
380 # ...
381 sub performAddressedReply {
382     return unless ($addressed);
383     &performReply(@_);
384 }
385
386 sub pSReply {
387     &performStrictReply(@_);
388 }
389
390 # Usage: &performStrictReply($reply);
391 sub performStrictReply {
392     my ($reply) = @_;
393
394     &checkMsgType($reply);
395
396     if ($msgType eq 'private') {
397         &msg($who, $reply);
398     } elsif ($msgType eq 'public') {
399         &say($reply);
400     } elsif ($msgType eq 'chat') {
401         &dccsay(lc $who, $reply);
402     } else {
403         &ERROR("pSR: msgType invalid? ($msgType).");
404     }
405 }
406
407 sub dccsay {
408     my($who, $reply) = @_;
409
410     if (!defined $reply or $reply =~ /^\s*$/) {
411         &WARN("dccsay: reply == NULL.");
412         return;
413     }
414
415     if (!exists $dcc{'CHAT'}{$who}) {
416         &VERB("pSR: dcc{'CHAT'}{$who} does not exist. (2)",2);
417         return;
418     }
419
420     &status("=>$who<= $reply");         # dcc chat.
421     $conn->privmsg($dcc{'CHAT'}{$who}, $reply);
422 }
423
424 sub dcc_close {
425     my($who) = @_;
426     my $type;
427
428     foreach $type (keys %dcc) {
429         &FIXME("dcc_close: $who");
430         my @who = grep /^\Q$who\E$/i, keys %{ $dcc{$type} };
431         next unless (scalar @who);
432         $who = $who[0];
433         &DEBUG("dcc_close... close $who!");
434     }
435 }
436
437 sub joinchan {
438     my ($chan)  = @_;
439     my $key     = &getChanConf("chankey", $chan) || "";
440
441     # forgot for about 2 years to implement channel keys when moving
442     # over to Net::IRC...
443
444     # hopefully validChan is right.
445     if (&validChan($chan)) {
446         &status("join: already on $chan");
447     } else {
448         &status("joining $b_blue$chan$ob");
449
450         return if ($conn->join($chan, $key));
451
452         &DEBUG("joinchan: join failed. trying connect!");
453         &clearIRCVars();
454         $conn->connect();
455     }
456 }
457
458 sub part {
459     my $chan;
460
461     foreach $chan (@_) {
462         next if ($chan eq "");
463         $chan =~ tr/A-Z/a-z/;   # lowercase.
464
465         if ($chan !~ /^$mask{chan}$/) {
466             &WARN("part: chan is invalid ($chan)");
467             next;
468         }
469
470         &status("parting $chan");
471         if (!&validChan($chan)) {
472             &WARN("part: not on $chan; doing anyway");
473 #           next;
474         }
475
476         $conn->part($chan);
477         # deletion of $channels{chan} is done in &entryEvt().
478     }
479 }
480
481 sub mode {
482     my ($chan, @modes) = @_;
483     my $modes = join(" ", @modes);
484
485     if (&validChan($chan) == 0) {
486         &ERROR("mode: invalid chan => '$chan'.");
487         return;
488     }
489
490     &DEBUG("mode: MODE $chan $modes");
491
492     # should move to use Net::IRC's $conn->mode()... but too lazy.
493     rawout("MODE $chan $modes");
494 }
495
496 sub op {
497     my ($chan, @who) = @_;
498     my $os      = "o" x scalar(@who);
499
500     &mode($chan, "+$os @who");
501 }
502
503 sub deop {
504     my ($chan, @who) = @_;
505     my $os = "o" x scalar(@who);
506
507     &mode($chan, "-$os ".@who);
508 }
509
510 sub kick {
511     my ($nick,$chan,$msg) = @_;
512     my (@chans) = ($chan eq "") ? (keys %channels) : lc($chan);
513
514     if ($chan ne "" and &validChan($chan) == 0) {
515         &ERROR("kick: invalid channel $chan.");
516         return;
517     }
518
519     $nick =~ tr/A-Z/a-z/;
520
521     foreach $chan (@chans) {
522         if (!&IsNickInChan($nick,$chan)) {
523             &status("kick: $nick is not on $chan.") if (scalar @chans == 1);
524             next;
525         }
526
527         if (!exists $channels{$chan}{o}{$ident}) {
528             &status("kick: do not have ops on $chan :(");
529             next;
530         }
531
532         &status("Kicking $nick from $chan.");
533         $conn->kick($chan, $nick, $msg);
534     }
535 }
536
537 sub ban {
538     my ($mask,$chan) = @_;
539     my (@chans) = ($chan =~ /^\*?$/) ? (keys %channels) : lc($chan);
540     my $ban     = 0;
541
542     if ($chan !~ /^\*?$/ and &validChan($chan) == 0) {
543         &ERROR("ban: invalid channel $chan.");
544         return;
545     }
546
547     foreach $chan (@chans) {
548         if (!exists $channels{$chan}{o}{$ident}) {
549             &status("ban: do not have ops on $chan :(");
550             next;
551         }
552
553         &status("Banning $mask from $chan.");
554         &rawout("MODE $chan +b $mask");
555         $ban++;
556     }
557
558     return $ban;
559 }
560
561 sub unban {
562     my ($mask,$chan) = @_;
563     my (@chans) = ($chan =~ /^\*?$/) ? (keys %channels) : lc($chan);
564     my $ban     = 0;
565
566     &DEBUG("unban: mask = $mask, chan = @chans");
567
568     foreach $chan (@chans) {
569         if (!exists $channels{$chan}{o}{$ident}) {
570             &status("unBan: do not have ops on $chan :(");
571             next;
572         }
573
574         &status("Removed ban $mask from $chan.");
575         &rawout("MODE $chan -b $mask");
576         $ban++;
577     }
578
579     return $ban;
580 }
581
582 sub quit {
583     my ($quitmsg) = @_;
584     &status("QUIT $param{'ircNick'} has quit IRC ($quitmsg)");
585     if (defined $conn) {
586         $conn->quit($quitmsg);
587     } else {
588         &WARN("quit: could not quit!");
589     }
590 }
591
592 sub nick {
593     my ($nick) = @_;
594
595     if (!defined $nick) {
596         &ERROR("nick: nick == NULL.");
597         return;
598     }
599
600     if (defined $ident and $nick eq $ident) {
601         &WARN("nick: nick == ident == '$ident'.");
602         return;
603     }
604
605     my $bad     = 0;
606     $bad++ if (exists $nuh{ $param{'ircNick'} });
607     $bad++ if (&IsNickInAnyChan($param{'ircNick'}));
608
609     if ($bad) {
610         &WARN("Nick: not going to try and get my nick back. [".
611                 scalar(gmtime). "]");
612 # hrm... over time we lose track of our own nick.
613 #       return;
614     }
615
616     if ($nick =~ /^$mask{nick}$/) {
617         &rawout("NICK ".$nick);
618
619         if (defined $ident) {
620             &status("nick: Changing nick to $nick (from $ident)");
621             # following shouldn't be here :(
622             $ident      = $nick;
623         } else {
624             &DEBUG("first time nick change.");
625             $ident      = $nick;
626         }
627
628         return 1;
629     }
630     &DEBUG("nick: failed... why oh why (nick => $nick)");
631
632     return 0;
633 }
634
635 sub invite {
636     my($who, $chan) = @_;
637     # todo: check if $who or $chan are invalid.
638
639     $conn->invite($who, $chan);
640 }
641
642 ##########
643 # Channel related functions...
644 #
645
646 # Usage: &joinNextChan();
647 sub joinNextChan {
648     if (scalar @joinchan) {
649         my $chan = shift @joinchan;
650         &joinchan($chan);
651
652         if (my $i = scalar @joinchan) {
653             &status("joinNextChan: $i chans to join.");
654         }
655
656         return;
657     }
658
659     # !scalar @joinchan:
660     my @c       = &getJoinChans();
661     if (exists $cache{joinTime} and scalar @c) {
662         my $delta       = time() - $cache{joinTime} - 5;
663         my $timestr     = &Time2String($delta);
664         my $rate        = sprintf("%.1f", $delta / @c);
665         delete $cache{joinTime};
666
667         &status("time taken to join all chans: $timestr; rate: $rate sec/join");
668     }
669
670     # chanserv check: global channels, in case we missed one.
671     foreach ( &ChanConfList("chanServ_ops") ) {
672         &chanServCheck($_);
673     }
674 }
675
676 # Usage: &getNickInChans($nick);
677 sub getNickInChans {
678     my ($nick) = @_;
679     my @array;
680
681     foreach (keys %channels) {
682         next unless (grep /^\Q$nick\E$/i, keys %{ $channels{$_}{''} });
683         push(@array, $_);
684     }
685
686     return @array;
687 }
688
689 # Usage: &getNicksInChan($chan);
690 sub getNicksInChan {
691     my ($chan) = @_;
692     my @array;
693
694     return keys %{ $channels{$chan}{''} };
695 }
696
697 sub IsNickInChan {
698     my ($nick,$chan) = @_;
699
700     $chan =~ tr/A-Z/a-z/;       # not lowercase unfortunately.
701
702     if (&validChan($chan) == 0) {
703         &ERROR("INIC: invalid channel $chan.");
704         return 0;
705     }
706
707     if (grep /^\Q$nick\E$/i, keys %{ $channels{$chan}{''} }) {
708         return 1;
709     } else {
710         foreach (keys %channels) {
711             next unless (/[A-Z]/);
712             &DEBUG("iNIC: hash channels contains mixed cased chan!!!");
713         }
714         return 0;
715     }
716 }
717
718 sub IsNickInAnyChan {
719     my ($nick) = @_;
720     my $chan;
721
722     foreach $chan (keys %channels) {
723         next unless (grep /^\Q$nick\E$/i, keys %{ $channels{$chan}{''}  });
724         return 1;
725     }
726     return 0;
727 }
728
729 # Usage: &validChan($chan);
730 sub validChan {
731     # todo: use $c instead?
732     my ($chan) = @_;
733
734     if (!defined $chan or $chan =~ /^\s*$/) {
735         return 0;
736     }
737
738     if (lc $chan ne $chan) {
739         &WARN("validChan: lc chan != chan. ($chan); fixing.");
740         $chan =~ tr/A-Z/a-z/;
741     }
742
743     # it's possible that this check creates the hash if empty.
744     if (defined $channels{$chan} or exists $channels{$chan}) {
745         if ($chan =~ /^_?default$/) {
746 #           &WARN("validC: chan cannot be _default! returning 0!");
747             return 0;
748         }
749
750         return 1;
751     } else {
752         return 0;
753     }
754 }
755
756 ###
757 # Usage: &delUserInfo($nick,@chans);
758 sub delUserInfo {
759     my ($nick,@chans) = @_;
760     my ($mode,$chan);
761
762     foreach $chan (@chans) {
763         foreach $mode (keys %{ $channels{$chan} }) {
764             # use grep here?
765             next unless (exists $channels{$chan}{$mode}{$nick});
766
767             delete $channels{$chan}{$mode}{$nick};
768         }
769     }
770 }
771
772 sub clearChanVars {
773     my ($chan) = @_;
774
775     delete $channels{$chan};
776 }
777
778 sub clearIRCVars {
779     undef %channels;
780     undef %floodjoin;
781
782     @joinchan           = &getJoinChans(1);
783     $cache{joinTime}    = time();
784 }
785
786 sub getJoinChans {
787     my($show)   = @_;
788     my @chans;
789     my @skip;
790
791     foreach (keys %chanconf) {
792         next if ($_ eq "_default");
793
794         my $val = $chanconf{$_}{autojoin};
795         my $skip = 0;
796
797         if (defined $val) {
798             $skip++ if ($val eq "0");
799         } else {
800             $skip++;
801         }
802
803         if ($skip) {
804             push(@skip, $_);
805             next;
806         }
807
808         push(@chans, $_);
809     }
810
811     my $str;
812     if (scalar @skip) {
813         $str = "channels not auto-joining: @skip (joining: @chans)";
814     } else {
815         $str = "auto-joining all chans: @chans";
816     }
817
818     &status("Chans: ".$str) if ($show);
819
820     return @chans;
821 }
822
823 sub closeDCC {
824 #    &DEBUG("closeDCC called.");
825     my $type;
826
827     foreach $type (keys %dcc) {
828         next if ($type ne uc($type));
829  
830         my $nick;
831         foreach $nick (keys %{ $dcc{$type} }) {
832             next unless (defined $nick);
833             &status("DCC CHAT: closing DCC $type to $nick.");
834             next unless (defined $dcc{$type}{$nick});
835
836             my $ref = $dcc{$type}{$nick};
837             &dccsay($nick, "bye bye, $nick") if ($type =~ /^chat$/i);
838             $dcc{$type}{$nick}->close();
839             delete $dcc{$type}{$nick};
840             &DEBUG("after close for $nick");
841         }
842         delete $dcc{$type};
843     }
844 }
845
846 sub joinfloodCheck {
847     my($who,$chan,$userhost) = @_;
848
849     return unless (&IsChanConf("joinfloodCheck"));
850
851     if (exists $netsplit{lc $who}) {    # netsplit join.
852         &DEBUG("joinfloodCheck: $who was in netsplit; not checking.");
853     }
854
855     if (exists $floodjoin{$chan}{$who}{Time}) {
856         &WARN("floodjoin{$chan}{$who} already exists?");
857     }
858
859     $floodjoin{$chan}{$who}{Time} = time();
860     $floodjoin{$chan}{$who}{Host} = $userhost;
861
862     ### Check...
863     foreach (keys %floodjoin) {
864         my $c = $_;
865         my $count = scalar keys %{ $floodjoin{$c} };
866         next unless ($count > 5);
867         &DEBUG("joinflood: count => $count");
868
869         my $time;
870         foreach (keys %{ $floodjoin{$c} }) {
871             my $t = $floodjoin{$c}{$_}{Time};
872             next unless (defined $t);
873
874             $time += $t;
875         }
876         &DEBUG("joinflood: time => $time");
877         $time /= $count;
878
879         &DEBUG("joinflood: new time => $time");
880     }
881
882     ### Clean it up.
883     my $delete = 0;
884     my $time = time();
885     foreach $chan (keys %floodjoin) {
886         foreach $who (keys %{ $floodjoin{$chan} }) {
887             my $t       = $floodjoin{$chan}{$who}{Time};
888             next unless (defined $t);
889
890             my $delta   = $time - $t;
891             next unless ($delta > 10);
892
893             delete $floodjoin{$chan}{$who};
894             $delete++;
895         }
896     }
897
898     &DEBUG("joinfloodCheck: $delete deleted.") if ($delete);
899 }
900
901 sub getHostMask {
902     my($n) = @_;
903
904     if (exists $nuh{$n}) {
905         return &makeHostMask($nuh{$n});
906     } else {
907         $cache{on_who_Hack} = 1;
908         $conn->who($n);
909     }
910 }
911
912 1;