]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/UserDCC.pl
show last item too
[infobot.git] / src / Modules / UserDCC.pl
1 #
2 #  UserDCC.pl: User Commands, DCC CHAT.
3 #      Author: dms
4 #     Version: v0.2 (20010119)
5 #     Created: 20000707 (from UserExtra.pl)
6 #
7
8 use strict;
9
10 use vars qw(%users %ignore %sched %bans %mask %cache %channels %param
11         %chanconf %dcc);
12 use vars qw($who $chan $message $msgType $user $chnick $conn $ident
13         $verifyUser $ucount_userfile $utime_userfile $lobotomized
14         $utime_chanfile $ucount_chanfile);
15 use vars qw(@backlog);
16
17 sub userDCC {
18     # hrm...
19     $message =~ s/\s+$//;
20
21     ### for all users.
22     # quit.
23     if ($message =~ /^(exit|quit)$/i) {
24         # do ircII clients support remote close? if so, cool!
25         &FIXME("userDCC: quit called.");
26         &dcc_close($who);
27         &status("userDCC: after dcc_close!");
28
29         return;
30     }
31
32     # who.
33     if ($message =~ /^who$/) {
34         my $count = scalar(keys %{ $dcc{'CHAT'} });
35         my $dccCHAT = $message;
36
37         &performStrictReply("Start of who ($count users).");
38         foreach (keys %{ $dcc{'CHAT'} }) {
39             &performStrictReply("=> $_");
40         }
41         &performStrictReply("End of who.");
42
43         return;
44     }
45
46     ### for those users with enough flags.
47
48     if ($message =~ /^tellme(\s+(.*))?$/i) {
49         my $args = $2;
50         if ($args =~ /^\s*$/) {
51             &help("tellme");
52             return;
53         }
54
55         my $result = &doQuestion($args);
56         &performStrictReply($result);
57
58         return;
59     }
60
61     # 4op.
62     if ($message =~ /^4op(\s+($mask{chan}))?$/i) {
63         return unless (&hasFlag("o"));
64
65         my $chan = $2;
66
67         if ($chan eq "") {
68             &help("4op");
69             return;
70         }
71
72         if (!$channels{$chan}{'o'}{$ident}) {
73             &msg($who, "i don't have ops on $chan to do that.");
74             return;
75         }
76
77         # on non-4mode(<4) servers, this may be exploited.
78         if ($channels{$chan}{'o'}{$who}) {
79             rawout("MODE $chan -o+o-o+o". (" $who" x 4));
80         } else {
81             rawout("MODE $chan +o-o+o-o". (" $who" x 4));
82         }
83
84         return;
85     }
86
87     # opme.
88     if ($message =~ /^opme(\s+($mask{chan}))?$/i) {
89         return unless (&hasFlag("o"));
90         return unless (&hasFlag("A"));
91
92         my $chan = $2;
93
94         if ($chan eq "") {
95             &help("4op");
96             return;
97         }
98
99         # can this be exploited?
100         rawout("MODE $chan +o $who");
101
102         return;
103     }
104
105     # backlog.
106     if ($message =~ /^backlog(\s+(.*))?$/i) {
107         return unless (&hasFlag("o"));
108         return unless (&IsParam("backlog"));
109         my $num = $2;
110         my $max = $param{'backlog'};
111
112         if (!defined $num) {
113             &help("backlog");
114             return;
115         } elsif ($num !~ /^\d+/) {
116             &msg($who, "error: argument is not positive integer.");
117             return;
118         } elsif ($num > $max or $num < 0) {
119             &msg($who, "error: argument is out of range (max $max).");
120             return;
121         }
122
123         &msg($who, "Start of backlog...");
124         for (0..$num-1) {
125             sleep 1 if ($_ % 4 == 0 and $_ != 0);
126             $conn->privmsg($who, "[".($_+1)."]: $backlog[$max-$num+$_]");
127         }
128         &msg($who, "End of backlog.");
129
130         return;
131     }
132
133     # dump variables.
134     if ($message =~ /^dumpvars$/i) {
135         return unless (&hasFlag("o"));
136         return unless (&IsParam("DumpVars"));
137
138         &status("Dumping all variables...");
139         &dumpallvars();
140
141         return;
142     }
143
144     # dump variables ][.
145     if ($message =~ /^symdump$/i) {
146         return unless (&hasFlag('o'));
147         return unless (&IsParam('DumpVars2'));
148
149         &status("Dumping all variables...");
150         &symdumpAllFile();
151
152         return;
153     }
154
155     # kick.
156     if ($message =~ /^kick(\s+(.*?))$/) {
157         return unless (&hasFlag("o"));
158
159         my $arg = $2;
160
161         if ($arg eq "") {
162             &help("kick");
163             return;
164         }
165         my @args = split(/\s+/, $arg);
166         my ($nick,$chan,$reason) = @args;
167
168         if (&validChan($chan) == 0) {
169             &msg($who,"error: invalid channel \002$chan\002");
170             return;
171         }
172
173         if (&IsNickInChan($nick,$chan) == 0) {
174             &msg($who,"$nick is not in $chan.");
175             return;
176         }
177
178         &kick($nick,$chan,$reason);
179
180         return;
181     }
182
183     # mode.
184     if ($message =~ /^mode(\s+(.*))?$/) {
185         return unless (&hasFlag("n"));
186         my ($chan,$mode) = split /\s+/,$2,2;
187
188         if ($chan eq "") {
189             &help("mode");
190             return;
191         }
192
193         if (&validChan($chan) == 0) {
194             &msg($who,"error: invalid channel \002$chan\002");
195             return;
196         }
197
198         if (!$channels{$chan}{o}{$ident}) {
199             &msg($who,"error: don't have ops on \002$chan\002");
200             return;
201         }
202
203         &mode($chan, $mode);
204
205         return;
206     }
207
208     # part.
209     if ($message =~ /^part(\s+(\S+))?$/i) {
210         return unless (&hasFlag("o"));
211         my $jchan = $2;
212
213         if ($jchan !~ /^$mask{chan}$/) {
214             &msg($who, "error, invalid chan.");
215             &help("part");
216             return;
217         }
218
219         if (!&validChan($jchan)) {
220             &msg($who, "error, I'm not on that chan.");
221             return;
222         }
223
224         &msg($jchan, "Leaving. (courtesy of $who).");
225         &part($jchan);
226         return;
227     }
228
229     # lobotomy. sometimes we want the bot to be _QUIET_.
230     if ($message =~ /^(lobotomy|bequiet)$/i) {
231         return unless (&hasFlag("o"));
232
233         if ($lobotomized) {
234             &performReply("i'm already lobotomized");
235         } else {
236             &performReply("i have been lobotomized");
237             $lobotomized = 1;
238         }
239
240         return;
241     }
242
243     # unlobotomy.
244     if ($message =~ /^(unlobotomy|benoisy)$/i) {
245         return unless (&hasFlag("o"));
246
247         if ($lobotomized) {
248             &performReply("i have been unlobotomized, woohoo");
249             $lobotomized = 0;
250             delete $cache{lobotomy};
251 #           undef $cache{lobotomy};     # ??
252         } else {
253             &performReply("i'm not lobotomized");
254         }
255
256         return;
257     }
258
259     # op.
260     if ($message =~ /^op(\s+(.*))?$/i) {
261         return unless (&hasFlag("o"));
262         my ($opee) = lc $2;
263         my @chans;
264
265         if ($opee =~ / /) {
266             if ($opee =~ /^(\S+)\s+(\S+)$/) {
267                 $opee  = $1;
268                 @chans = ($2);
269                 if (!&validChan($2)) {
270                     &msg($who,"error: invalid chan ($2).");
271                     return;
272                 }
273             } else {
274                 &msg($who,"error: invalid params.");
275                 return;
276             }
277         } else {
278             @chans = keys %channels;
279         }
280
281         my $found = 0;
282         my $op = 0;
283         foreach (@chans) {
284             next unless (&IsNickInChan($opee,$_));
285             $found++;
286             if ($channels{$_}{'o'}{$opee}) {
287                 &performStrictReply("op: $opee already has ops on $_");
288                 next;
289             }
290             $op++;
291
292             &performStrictReply("opping $opee on $_");
293             &op($_, $opee);
294         }
295
296         if ($found != $op) {
297             &performStrictReply("op: opped on all possible channels.");
298         } else {
299             &DEBUG("op: found => '$found'.");
300             &DEBUG("op:    op => '$op'.");
301         }
302
303         return;
304     }
305
306     # deop.
307     if ($message =~ /^deop(\s+(.*))?$/i) {
308         return unless (&hasFlag("o"));
309         my ($opee) = lc $2;
310         my @chans;
311
312         if ($opee =~ / /) {
313             if ($opee =~ /^(\S+)\s+(\S+)$/) {
314                 $opee  = $1;
315                 @chans = ($2);
316                 if (!&validChan($2)) {
317                     &msg($who,"error: invalid chan ($2).");
318                     return;
319                 }
320             } else {
321                 &msg($who,"error: invalid params.");
322                 return;
323             }
324         } else {
325             @chans = keys %channels;
326         }
327
328         my $found = 0;
329         my $op = 0;
330         foreach (@chans) {
331             next unless (&IsNickInChan($opee,$_));
332             $found++;
333             if (!exists $channels{$_}{'o'}{$opee}) {
334                 &status("deop: $opee already has no ops on $_");
335                 next;
336             }
337             $op++;
338
339             &status("deopping $opee on $_ at ${who}'s request");
340             &deop($_, $opee);
341         }
342
343         if ($found != $op) {
344             &status("deop: deopped on all possible channels.");
345         } else {
346             &DEBUG("deop: found => '$found'.");
347             &DEBUG("deop: op => '$op'.");
348         }
349
350         return;
351     }
352
353     # say.
354     if ($message =~ s/^say\s+(\S+)\s+(.*)//) {
355         return unless (&hasFlag("o"));
356         my ($chan,$msg) = (lc $1, $2);
357
358         &DEBUG("chan => '$1', msg => '$msg'.");
359
360         # TODO: add nick destination.
361         if (&validChan($chan)) {
362             &msg($chan, $msg);
363         } else {
364             &msg($who,"i'm not on \002$chan\002, sorry.");
365         }
366
367         return;
368     }
369
370     # do.
371     if ($message =~ s/^do\s+(\S+)\s+(.*)//) {
372         return unless (&hasFlag("o"));
373         my ($chan,$msg) = (lc $1, $2);
374
375         &DEBUG("chan => '$1', msg => '$msg'.");
376
377         # TODO: add nick destination.
378         if (&validChan($chan)) {
379             &action($chan, $msg);
380         } else {
381             &msg($who,"i'm not on \002$chan\002, sorry.");
382         }
383
384         return;
385     }
386
387     # die.
388     if ($message =~ /^die$/) {
389         return unless (&hasFlag("n"));
390
391         &doExit();
392
393         &status("Dying by $who\'s request");
394         exit 0;
395     }
396
397     # global factoid substitution.
398     if ($message =~ m|^s([/,#])(.+?)\1(.*?)\1;?\s*$|) {
399         my ($delim,$op,$np) = ($1, $2, $3);
400         return unless (&hasFlag("n"));
401         ### TODO: support flags to do full-on global.
402
403         # incorrect format.
404         if ($np =~ /$delim/) {
405             &performReply("looks like you used the delimiter too many times. You may want to use a different delimiter, like ':' or '#'.");
406             return;
407         }
408
409         ### TODO: fix up $op to support mysql/sqlite/pgsql
410         ### TODO: => add db/sql specific function to fix this.
411         my @list = &searchTable("factoids", "factoid_key",
412                         "factoid_value", $op);
413
414         if (!scalar @list) {
415             &performReply("Expression didn't match anything.");
416             return;
417         }
418
419         if (scalar @list > 100) {
420             &performReply("regex found more than 100 matches... not doing.");
421             return;
422         }
423
424         &status("gsubst: going to alter ".scalar(@list)." factoids.");
425         &performReply("going to alter ".scalar(@list)." factoids.");
426
427         my $error = 0;
428         foreach (@list) {
429             my $faqtoid = $_;
430
431             next if (&IsLocked($faqtoid) == 1);
432             my $result = &getFactoid($faqtoid);
433             my $was = $result;
434             &DEBUG("was($faqtoid) => '$was'.");
435
436             # global global
437             # we could support global local (once off).
438             if ($result =~ s/\Q$op/$np/gi) {
439                 if (length $result > $param{'maxDataSize'}) {
440                     &performReply("that's too long (or was long)");
441                     return;
442                 }
443                 &setFactInfo($faqtoid, "factoid_value", $result);
444                 &status("update: '$faqtoid' =is=> '$result'; was '$was'");
445             } else {
446                 &WARN("subst: that's weird... thought we found the string ($op) in '$faqtoid'.");
447                 $error++;
448             }
449         }
450
451         if ($error) {
452             &ERROR("Some warnings/errors?");
453         }
454
455         &performReply("Ok... did s/$op/$np/ for ".
456                                 (scalar(@list) - $error)." factoids");
457
458         return;
459     }
460
461     # jump.
462     if ($message =~ /^jump(\s+(\S+))?$/i) {
463         return unless (&hasFlag("n"));
464
465         if ($2 eq "") {
466             &help("jump");
467             return;
468         }
469
470         my ($server,$port);
471         if ($2 =~ /^(\S+)(:(\d+))?$/) {
472             $server = $1;
473             $port   = $3 || 6667;
474         } else {
475             &msg($who,"invalid format.");
476             return;
477         }
478
479         &status("jumping servers... $server...");
480         $conn->quit("jumping to $server");
481
482         if (&irc($server,$port) == 0) {
483             &ircloop();
484         }
485     }
486
487     # reload.
488     if ($message =~ /^reload$/i) {
489         return unless (&hasFlag("n"));
490
491         &status("USER reload $who");
492         &performStrictReply("reloading...");
493         &reloadAllModules();
494         &performStrictReply("reloaded.");
495
496         return;
497     }
498
499     # reset.
500     if ($message =~ /^reset$/i) {
501         return unless (&hasFlag("n"));
502
503         &msg($who,"resetting...");
504         my @done;
505         foreach ( keys %channels, keys %chanconf ) {
506             my $c = $_;
507             next if (grep /^\Q$c\E$/i, @done);
508
509             &part($_);
510
511             push(@done, $_);
512             sleep 1;
513         }
514         &DEBUG("before clearircvars");
515         &clearIRCVars();
516         &DEBUG("before joinnextchan");
517         &joinNextChan();
518         &DEBUG("after joinnextchan");
519
520         &status("USER reset $who");
521         &msg($who,"reset complete");
522
523         return;
524     }
525
526     # rehash.
527     if ($message =~ /^rehash$/) {
528         return unless (&hasFlag("n"));
529
530         &msg($who,"rehashing...");
531         &restart("REHASH");
532         &status("USER rehash $who");
533         &msg($who,"rehashed");
534
535         return;
536     }
537
538     #####
539     ##### USER//CHAN SPECIFIC CONFIGURATION COMMANDS
540     #####
541
542     if ($message =~ /^chaninfo(\s+(.*))?$/) {
543         my @args = split /[\s\t]+/, $2; # hrm.
544
545         if (scalar @args != 1) {
546             &help("chaninfo");
547             return;
548         }
549
550         if (!exists $chanconf{$args[0]}) {
551             &performStrictReply("no such channel $args[0]");
552             return;
553         }
554
555         &performStrictReply("showing channel conf.");
556         foreach (sort keys %{ $chanconf{$args[0]} }) {
557             &performStrictReply("$chan: $_ => $chanconf{$args[0]}{$_}");
558         }
559         &performStrictReply("End of chaninfo.");
560
561         return;
562     }
563
564     # +chan.
565     if ($message =~ /^(chanset|\+chan)(\s+(.*?))?$/) {
566         my $cmd         = $1;
567         my $args        = $3;
568         my $no_chan     = 0;
569
570         if (!defined $args) {
571             &help($cmd);
572             return;
573         }
574
575         my @chans;
576         while ($args =~ s/^($mask{chan})\s*//) {
577             push(@chans, lc($1));
578         }
579
580         if (!scalar @chans) {
581             push(@chans, "_default");
582             $no_chan    = 1;
583         }
584
585         my($what,$val) = split /[\s\t]+/, $args, 2;
586
587         ### TODO: "cannot set values without +m".
588         return unless (&hasFlag("n"));
589
590         # READ ONLY.
591         if (defined $what and $what !~ /^[-+]/ and !defined $val and $no_chan) {
592             &performStrictReply("Showing $what values on all channels...");
593
594             my %vals;
595             foreach (keys %chanconf) {
596                 my $val;
597                 if (defined $chanconf{$_}{$what}) {
598                     $val = $chanconf{$_}{$what};
599                 } else {
600                     $val = "NOT-SET";
601                 }
602                 $vals{$val}{$_} = 1;
603             }
604
605             foreach (keys %vals) {
606                 &performStrictReply("  $what = $_(" . scalar(keys %{$vals{$_}}) . "): ".join(' ', sort keys %{ $vals{$_} } ) );
607             }
608
609             &performStrictReply("End of list.");
610
611             return;
612         }
613
614         ### TODO: move to UserDCC again.
615         if ($cmd eq "chanset" and !defined $what) {
616             &DEBUG("showing channel conf.");
617
618             foreach $chan (@chans) {
619                 if ($chan eq '_default') {
620                     &performStrictReply("Default channel settings");
621                 } else {
622                     &performStrictReply("chan: $chan (see _default also)");
623                 }
624                 my @items;
625                 my $str = "";
626                 foreach (sort keys %{ $chanconf{$chan} }) {
627                     my $newstr = join(', ', @items);
628                     ### TODO: make length use channel line limit?
629                     if (length $newstr > 370) {
630                         &performStrictReply(" $str");
631                         @items = ();
632                     }
633                     $str = $newstr;
634                     push(@items, "$_ => $chanconf{$chan}{$_}");
635                 }
636                 if (@items) {
637                     my $str = join(', ', @items);
638                     &performStrictReply(" $str");
639                 }
640             }
641             return;
642         }
643
644         $cache{confvars}{$what} = $val;
645         &rehashConfVars();
646
647         foreach (@chans) {
648             &chanSet($cmd, $_, $what, $val);
649         }
650
651         return;
652     }
653
654     if ($message =~ /^(chanunset|\-chan)(\s+(.*))?$/) {
655         return unless (&hasFlag("n"));
656         my $args        = $3;
657         my $no_chan     = 0;
658
659         if (!defined $args) {
660             &help("chanunset");
661             return;
662         }
663
664         my ($chan);
665         my $delete      = 0;
666         if ($args =~ s/^(\-)?($mask{chan})\s*//) {
667             $chan       = $2;
668             $delete     = ($1) ? 1 : 0;
669         } else {
670             &VERB("no chan arg; setting to default.",2);
671             $chan       = "_default";
672             $no_chan    = 1;
673         }
674
675         if (!exists $chanconf{$chan}) {
676             &performStrictReply("no such channel $chan");
677             return;
678         }
679
680         if ($args ne "") {
681
682             if (!&getChanConf($args,$chan)) {
683                 &performStrictReply("$args does not exist for $chan");
684                 return;
685             }
686
687             my @chans = &ChanConfList($args);
688             &DEBUG("scalar chans => ".scalar(@chans) );
689             if (scalar @chans == 1 and $chans[0] eq "_default" and !$no_chan) {
690                 &performStrictReply("ok, $args was set only for _default; unsetting for _defaul but setting for other chans.");
691
692                 my $val = $chanconf{$_}{_default};
693                 foreach (keys %chanconf) {
694                     $chanconf{$_}{$args} = $val;
695                 }
696                 delete $chanconf{_default}{$args};
697                 $cache{confvars}{$args} = 0;
698                 &rehashConfVars();
699
700                 return;
701             }
702
703             if ($no_chan and !exists($chanconf{_default}{$args})) {
704                 &performStrictReply("ok, $args for _default does not exist, removing from all chans.");
705
706                 foreach (keys %chanconf) {
707                     next unless (exists $chanconf{$_}{$args});
708                     &DEBUG("delete chanconf{$_}{$args};");
709                     delete $chanconf{$_}{$args};
710                 }
711                 $cache{confvars}{$args} = 0;
712                 &rehashConfVars();
713
714                 return;
715             }
716
717             &performStrictReply("Unsetting channel ($chan) option $args. (was $chanconf{$chan}{$args})");
718             delete $chanconf{$chan}{$args};
719
720             return;
721         }
722
723         if ($delete) {
724             &performStrictReply("Deleting channel $chan for sure!");
725             $utime_chanfile = time();
726             $ucount_chanfile++;
727
728             &part($chan);
729             &performStrictReply("Leaving $chan...");
730
731             delete $chanconf{$chan};
732         } else {
733             &performStrictReply("Prefix channel with '-' to delete for sure.");
734         }
735
736         return;
737     }
738
739     if ($message =~ /^newpass(\s+(.*))?$/) {
740         my(@args) = split /[\s\t]+/, $2 || '';
741
742         if (scalar @args != 1) {
743             &help("newpass");
744             return;
745         }
746
747         my $u = &getUser($who);
748         my $crypt = &mkcrypt($args[0]);
749
750         &performStrictReply("Set your passwd to '$crypt'");
751         $users{$u}{PASS} = $crypt;
752
753         $utime_userfile = time();
754         $ucount_userfile++;
755
756         return;
757     }
758
759     if ($message =~ /^chpass(\s+(.*))?$/) {
760         my(@args) = split /[\s\t]+/, $2 || '';
761
762         if (!scalar @args) {
763             &help("chpass");
764             return;
765         }
766
767         if (!&IsUser($args[0])) {
768             &performStrictReply("user $args[0] is not valid.");
769             return;
770         }
771
772         my $u = &getUser($args[0]);
773         if (!defined $u) {
774             &performStrictReply("Internal error, u = NULL.");
775             return;
776         }
777
778         if (scalar @args == 1) {
779             # del pass.
780             if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
781                 &performStrictReply("cannot remove passwd of others.");
782                 return;
783             }
784
785             if (!exists $users{$u}{PASS}) {
786                 &performStrictReply("$u does not have pass set anyway.");
787                 return;
788             }
789
790             &performStrictReply("Deleted pass from $u.");
791
792             $utime_userfile = time();
793             $ucount_userfile++;
794
795             delete $users{$u}{PASS};
796
797             return;
798         }
799
800         my $crypt       = &mkcrypt($args[1]);
801         &performStrictReply("Set $u's passwd to '$crypt'");
802         $users{$u}{PASS} = $crypt;
803
804         $utime_userfile = time();
805         $ucount_userfile++;
806
807         return;
808     }
809
810     if ($message =~ /^chattr(\s+(.*))?$/) {
811         my(@args) = split /[\s\t]+/, $2 || '';
812
813         if (!scalar @args) {
814             &help("chattr");
815             return;
816         }
817
818         my $chflag;
819         my $user;
820         if ($args[0] =~ /^$mask{nick}$/i) {
821             # <nick>
822             $user       = &getUser($args[0]);
823             $chflag     = $args[1];
824         } else {
825             # <flags>
826             $user       = &getUser($who);
827             &DEBUG("user $who... nope.") unless (defined $user);
828             $user       = &getUser($verifyUser);
829             $chflag     = $args[0];
830         }
831
832         if (!defined $user) {
833             &performStrictReply("user does not exist.");
834             return;
835         }
836
837         my $flags = $users{$user}{FLAGS};
838         if (!defined $chflag) {
839             &performStrictReply("Flags for $user: $flags");
840             return;
841         }
842
843         &DEBUG("who => $who");
844         &DEBUG("verifyUser => $verifyUser");
845         if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
846             &performStrictReply("cannto change attributes of others.");
847             return "REPLY";
848         }
849
850         my $state;
851         my $change      = 0;
852         foreach (split //, $chflag) {
853             if ($_ eq "+") { $state = 1; next; }
854             if ($_ eq "-") { $state = 0; next; }
855
856             if (!defined $state) {
857                 &performStrictReply("no initial + or - was found in attr.");
858                 return;
859             }
860
861             if ($state) {
862                 next if ($flags =~ /\Q$_\E/);
863                 $flags .= $_;
864             } else {
865                 if (&IsParam("owner")
866                         and $param{owner} =~ /^\Q$user\E$/i
867                         and $flags =~ /[nmo]/
868                 ) {
869                     &performStrictReply("not removing flag $_ for $user.");
870                     next;
871                 }
872                 next unless ($flags =~ s/\Q$_\E//);
873             }
874
875             $change++;
876         }
877
878         if ($change) {
879             $utime_userfile = time();
880             $ucount_userfile++;
881             #$flags.*FLAGS sort
882             $flags = join('', sort split('', $flags));
883             &performStrictReply("Current flags: $flags");
884             $users{$user}{FLAGS} = $flags;
885         } else {
886             &performStrictReply("No flags changed: $flags");
887         }
888
889         return;
890     }
891
892     if ($message =~ /^chnick(\s+(.*))?$/) {
893         my(@args) = split /[\s\t]+/, $2 || '';
894
895         if ($who eq "_default") {
896             &WARN("$who or verifyuser tried to run chnick.");
897             return "REPLY";
898         }
899
900         if (!scalar @args or scalar @args > 2) {
901             &help("chnick");
902             return;
903         }
904
905         if (scalar @args == 1) {        # 1
906             $user       = &getUser($who);
907             &DEBUG("nope, not $who.") unless (defined $user);
908             $user       ||= &getUser($verifyUser);
909             $chnick     = $args[0];
910         } else {                        # 2
911             $user       = &getUser($args[0]);
912             $chnick     = $args[1];
913         }
914
915         if (!defined $user) {
916             &performStrictReply("user $who or $args[0] does not exist.");
917             return;
918         }
919
920         if ($user =~ /^\Q$chnick\E$/i) {
921             &performStrictReply("user == chnick. why should I do that?");
922             return;
923         }
924
925         if (&getUser($chnick)) {
926             &performStrictReply("user $chnick is already used!");
927             return;
928         }
929
930         if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
931             &performStrictReply("cannto change nick of others.");
932             return "REPLY" if ($who eq "_default");
933             return;
934         }
935
936         foreach (keys %{ $users{$user} }) {
937             $users{$chnick}{$_} = $users{$user}{$_};
938             delete $users{$user}{$_};
939         }
940         undef $users{$user};    # ???
941
942         $utime_userfile = time();
943         $ucount_userfile++;
944
945         &performStrictReply("Changed '$user' to '$chnick' successfully.");
946
947         return;
948     }
949
950     if ($message =~ /^([-+])host(\s+(.*))?$/) {
951         my $cmd         = $1."host";
952         my(@args)       = split /[\s\t]+/, $3 || '';
953         my $state       = ($1 eq "+") ? 1 : 0;
954
955         if (!scalar @args) {
956             &help($cmd);
957             return;
958         }
959
960         if ($who eq "_default") {
961             &WARN("$who or verifyuser tried to run $cmd.");
962             return "REPLY";
963         }
964
965         my ($user,$mask);
966         if ($args[0] =~ /^$mask{nick}$/i) {     # <nick>
967             return unless (&hasFlag("n"));
968             $user       = &getUser($args[0]);
969             $mask       = $args[1];
970         } else {                                # <mask>
971             # FIXME: who or verifyUser. (don't remember why)
972             $user       = &getUser($who);
973             $mask       = $args[0];
974         }
975
976         if (!defined $user) {
977             &performStrictReply("user $user does not exist.");
978             return;
979         }
980
981         if (!defined $mask) {
982             &performStrictReply("Hostmasks for $user: " . join(" ", keys %{$users{$user}{HOSTS}}));
983             return;
984         }
985
986         if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
987             &performStrictReply("cannto change masks of others.");
988             return;
989         }
990
991         my $count = scalar keys %{ $users{$user}{HOSTS} };
992
993         if ($state) {                           # add.
994             if ($mask !~ /^$mask{nuh}$/) {
995                 &performStrictReply("error: mask ($mask) is not a real hostmask.");
996                 return;
997             }
998
999             if (exists $users{$user}{HOSTS}{$mask}) {
1000                 &performStrictReply("mask $mask already exists.");
1001                 return;
1002             }
1003
1004             ### TODO: override support.
1005             $users{$user}{HOSTS}{$mask} = 1;
1006
1007             &performStrictReply("Added $mask to list of masks.");
1008
1009         } else {                                # delete.
1010
1011             if (!exists $users{$user}{HOSTS}{$mask}) {
1012                 &performStrictReply("mask $mask does not exist.");
1013                 return;
1014             }
1015
1016             ### TODO: wildcard support. ?
1017             delete $users{$user}{HOSTS}{$mask};
1018
1019             if (scalar keys %{ $users{$user}{HOSTS} } != $count) {
1020                 &performStrictReply("Removed $mask from list of masks.");
1021             } else {
1022                 &performStrictReply("error: could not find $mask in list of masks.");
1023                 return;
1024             }
1025         }
1026
1027         $utime_userfile = time();
1028         $ucount_userfile++;
1029
1030         return;
1031     }
1032
1033     if ($message =~ /^([-+])ban(\s+(.*))?$/) {
1034         my $cmd         = $1."ban";
1035         my $flatarg     = $3;
1036         my(@args)       = split /[\s\t]+/, $3 || '';
1037         my $state       = ($1 eq "+") ? 1 : 0;
1038
1039         if (!scalar @args) {
1040             &help($cmd);
1041             return;
1042         }
1043
1044         my($mask,$chan,$time,$reason);
1045
1046         if ($flatarg =~ s/^($mask{nuh})\s*//) {
1047             $mask = $1;
1048         } else {
1049             &DEBUG("arg does not contain nuh mask?");
1050         }
1051
1052         if ($flatarg =~ s/^($mask{chan})\s*//) {
1053             $chan = $1;
1054         } else {
1055             $chan = "*";        # _default instead?
1056         }
1057
1058         if ($state == 0) {              # delete.
1059             my @c = &banDel($mask);
1060
1061             foreach (@c) {
1062                 &unban($mask, $_);
1063             }
1064
1065             if (@c) {
1066                 &performStrictReply("Removed $mask from chans: @c");
1067             } else {
1068                 &performStrictReply("$mask was not found in ban list.");
1069             }
1070
1071             return;
1072         }
1073
1074         ###
1075         # add ban.
1076         ###
1077
1078         # time.
1079         if ($flatarg =~ s/^(\d+)\s*//) {
1080             $time = $1;
1081             &DEBUG("time = $time.");
1082             if ($time < 0) {
1083                 &performStrictReply("error: time cannot be negatime?");
1084                 return;
1085             }
1086         } else {
1087             $time = 0;
1088         }
1089
1090         if ($flatarg =~ s/^(.*)$//) {   # need length?
1091             $reason     = $1;
1092         }
1093
1094         if (!&IsFlag("n") and $who !~ /^\Q$verifyUser\E$/i) {
1095             &performStrictReply("cannto change masks of others.");
1096             return;
1097         }
1098
1099         if ($mask !~ /^$mask{nuh}$/) {
1100             &performStrictReply("error: mask ($mask) is not a real hostmask.");
1101             return;
1102         }
1103
1104         if ( &banAdd($mask,$chan,$time,$reason) == 2) {
1105             &performStrictReply("ban already exists; overwriting.");
1106         }
1107         &performStrictReply("Added $mask for $chan (time => $time, reason => $reason)");
1108
1109         return;
1110     }
1111
1112     if ($message =~ /^whois(\s+(.*))?$/) {
1113         my $arg = $2;
1114
1115         if (!defined $arg) {
1116             &help("whois");
1117             return;
1118         }
1119
1120         my $user = &getUser($arg);
1121         if (!defined $user) {
1122             &performStrictReply("whois: user $user does not exist.");
1123             return;
1124         }
1125
1126         ### TODO: better (eggdrop-like) output.
1127         &performStrictReply("user: $user");
1128         foreach (keys %{ $users{$user} }) {
1129             my $ref = ref $users{$user}{$_};
1130
1131             if ($ref eq "HASH") {
1132                 my $type = $_;
1133                 ### DOES NOT WORK???
1134                 foreach (keys %{ $users{$user}{$type} }) {
1135                     &performStrictReply("    $type => $_");
1136                 }
1137                 next;
1138             }
1139
1140             &performStrictReply("    $_ => $users{$user}{$_}");
1141         }
1142         &performStrictReply("End of USER whois.");
1143
1144         return;
1145     }
1146
1147     if ($message =~ /^bans(\s+(.*))?$/) {
1148         my $arg = $2;
1149
1150         if (defined $arg) {
1151             if ($arg ne "_default" and !&validChan($arg) ) {
1152                 &performStrictReply("error: chan $chan is invalid.");
1153                 return;
1154             }
1155         }
1156
1157         if (!scalar keys %bans) {
1158             &performStrictReply("Ban list is empty.");
1159             return;
1160         }
1161
1162         my $c;
1163         &performStrictReply("     mask: expire, time-added, count, who-by, reason");
1164         foreach $c (keys %bans) {
1165             next unless (!defined $arg or $arg =~ /^\Q$c\E$/i);
1166             &performStrictReply("  $c:");
1167
1168             foreach (keys %{ $bans{$c} }) {
1169                 my $val = $bans{$c}{$_};
1170
1171                 if (ref $val eq "ARRAY") {
1172                     my @array = @{ $val };
1173                     &performStrictReply("    $_: @array");
1174                 } else {
1175                     &DEBUG("unknown ban: $val");
1176                 }
1177             }
1178         }
1179         &performStrictReply("END of bans.");
1180
1181         return;
1182     }
1183
1184     if ($message =~ /^banlist(\s+(.*))?$/) {
1185         my $arg = $2;
1186
1187         if (defined $arg and $arg !~ /^$mask{chan}$/) {
1188             &performStrictReply("error: chan $chan is invalid.");
1189             return;
1190         }
1191
1192         &DEBUG("bans for global or arg => $arg.");
1193         foreach (keys %bans) {                  #CHANGE!!!
1194             &DEBUG("  $_ => $bans{$_}.");
1195         }
1196
1197         &DEBUG("End of bans.");
1198         &performStrictReply("END of bans.");
1199
1200         return;
1201     }
1202
1203     if ($message =~ /^save$/) {
1204         return unless (&hasFlag("o"));
1205
1206         &writeUserFile();
1207         &writeChanFile();
1208         &performStrictReply("saved user and chan files");
1209
1210         return;
1211     }
1212
1213     ### ALIASES.
1214     $message =~ s/^addignore/+ignore/;
1215     $message =~ s/^(del|un)ignore/-ignore/;
1216
1217     # ignore.
1218     if ($message =~ /^(\+|\-)ignore(\s+(.*))?$/i) {
1219         return unless (&hasFlag("o"));
1220         my $state       = ($1 eq "+") ? 1 : 0;
1221         my $str         = $1."ignore";
1222         my $args        = $3;
1223
1224         if (!$args) {
1225             &help($str);
1226             return;
1227         }
1228
1229         my($mask,$chan,$time,$comment);
1230
1231         # mask.
1232         if ($args =~ s/^($mask{nuh})\s*//) {
1233             $mask = $1;
1234         } else {
1235             &ERROR("no NUH mask?");
1236             return;
1237         }
1238
1239         if (!$state) {                  # delignore.
1240             if ( &ignoreDel($mask) ) {
1241                 &performStrictReply("ok, deleted ignores for $mask.");
1242             } else {
1243                 &performStrictReply("could not find $mask in ignore list.");
1244             }
1245             return;
1246         }
1247
1248         ###
1249         # addignore.
1250         ###
1251
1252         # chan.
1253         if ($args =~ s/^($mask{chan}|\*)\s*//) {
1254             $chan = $1;
1255         } else {
1256             $chan = "*";
1257         }
1258
1259         # time.
1260         if ($args =~ s/^(\d+)\s*//) {
1261             $time = $1; # time is in minutes
1262         } else {
1263             $time = 0;
1264         }
1265
1266         # time.
1267         if ($args) {
1268             $comment = $args;
1269         } else {
1270             $comment = "added by $who";
1271         }
1272
1273         if ( &ignoreAdd($mask, $chan, $time, $comment) > 1) {
1274             &performStrictReply("FIXME: $mask already in ignore list; written over anyway.");
1275         } else {
1276             &performStrictReply("added $mask to ignore list.");
1277         }
1278
1279         return;
1280     }
1281
1282     if ($message =~ /^ignore(\s+(.*))?$/) {
1283         my $arg = $2;
1284
1285         if (defined $arg) {
1286             if ($arg !~ /^$mask{chan}$/) {
1287                 &performStrictReply("error: chan $chan is invalid.");
1288                 return;
1289             }
1290
1291             if (!&validChan($arg)) {
1292                 &performStrictReply("error: chan $arg is invalid.");
1293                 return;
1294             }
1295
1296             &performStrictReply("Showing bans for $arg only.");
1297         }
1298
1299         if (!scalar keys %ignore) {
1300             &performStrictReply("Ignore list is empty.");
1301             return;
1302         }
1303
1304         ### TODO: proper (eggdrop-like) formatting.
1305         my $c;
1306         &performStrictReply("    mask: expire, time-added, who, comment");
1307         foreach $c (keys %ignore) {
1308             next unless (!defined $arg or $arg =~ /^\Q$c\E$/i);
1309             &performStrictReply("  $c:");
1310
1311             foreach (keys %{ $ignore{$c} }) {
1312                 my $ref = ref $ignore{$c}{$_};
1313                 if ($ref eq "ARRAY") {
1314                     my @array = @{ $ignore{$c}{$_} };
1315                     &performStrictReply("      $_: @array");
1316                 } else {
1317                     &DEBUG("unknown ignore line?");
1318                 }
1319             }
1320         }
1321         &performStrictReply("END of ignore.");
1322
1323         return;
1324     }
1325
1326     # adduser/deluser.
1327     if ($message =~ /^(add|del)user(\s+(.*))?$/i) {
1328         my $str         = $1;
1329         my $strstr      = $1."user";
1330         my @args        = split /\s+/, $3 || '';
1331         my $args        = $3;
1332         my $state       = ($str =~ /^(add)$/) ? 1 : 0;
1333
1334         if (!scalar @args) {
1335             &help($strstr);
1336             return;
1337         }
1338
1339         if ($str eq 'add') {
1340             if (scalar @args != 2) {
1341                 &performStrictReply('adduser requires hostmask argument.');
1342                 return;
1343             }
1344         } elsif (scalar @args != 1) {
1345             &performStrictReply('too many arguments.');
1346             return;
1347         }
1348
1349         if ($state) {
1350             # adduser.
1351             if (scalar @args == 1) {
1352                 $args[1]        = &getHostMask($args[0]);
1353                 &performStrictReply("Attemping to guess $args[0]'s hostmask...");
1354
1355                 # crude hack... crappy Net::IRC
1356                 $conn->schedule(5, sub {
1357                     # hopefully this is right.
1358                     my $nick = (keys %{ $cache{nuhInfo} })[0];
1359                     if (!defined $nick) {
1360                         &performStrictReply("couldn't get nuhinfo... adding user without a hostmask.");
1361                         &userAdd($nick);
1362                         return;
1363                     }
1364                     my $mask = &makeHostMask( $cache{nuhInfo}{$nick}{NUH} );
1365
1366                     if ( &userAdd($nick, $mask) ) {
1367                         # success.
1368                         &performStrictReply("Added $nick with flags $users{$nick}{FLAGS}");
1369                         my @hosts = keys %{ $users{$nick}{HOSTS} };
1370                         &performStrictReply("hosts: @hosts");
1371                     }
1372                 });
1373                 return;
1374             }
1375
1376             &DEBUG("args => @args");
1377             if ( &userAdd(@args) ) {    # success.
1378                 &performStrictReply("Added $args[0] with flags $users{$args[0]}{FLAGS}");
1379                 my @hosts = keys %{ $users{$args[0]}{HOSTS} };
1380                 &performStrictReply("hosts: @hosts");
1381
1382             } else {                    # failure.
1383                 &performStrictReply("User $args[0] already exists");
1384             }
1385
1386         } else {                        # deluser.
1387
1388             if ( &userDel($args[0]) ) { # success.
1389                 &performStrictReply("Deleted $args[0] successfully.");
1390
1391             } else {                    # failure.
1392                 &performStrictReply("User $args[0] does not exist.");
1393             }
1394
1395         }
1396         return;
1397     }
1398
1399     if ($message =~ /^sched$/) {
1400         my @list;
1401         my @run;
1402
1403         my %time;
1404         foreach (keys %sched) {
1405             next unless (exists $sched{$_}{TIME});
1406             $time{ $sched{$_}{TIME}-time() }{$_} = 1;
1407             push(@list,$_);
1408
1409             next unless (exists $sched{$_}{RUNNING});
1410             push(@run,$_);
1411         }
1412
1413         my @time;
1414         foreach (sort { $a <=> $b } keys %time) {
1415             my $str = join(", ", sort keys %{ $time{$_} });
1416             &DEBUG("time => $_, str => $str");
1417             push(@time, "$str (".&Time2String($_).")");
1418         }
1419
1420         &performStrictReply( &formListReply(0, "Schedulers: ", @time ) );
1421         &performStrictReply( &formListReply(0, "Scheds to run: ", sort @list ) );
1422         &performStrictReply( &formListReply(0, "Scheds running(should not happen?) ", sort @run ) );
1423
1424         return;
1425     }
1426
1427     # quite a cool hack: reply in DCC CHAT.
1428     $msgType = "chat" if (exists $dcc{'CHAT'}{$who});
1429
1430     my $done = 0;
1431     $done++ if &parseCmdHook($message);
1432     $done++ unless (&Modules());
1433
1434     if ($done) {
1435         &DEBUG("running non DCC CHAT command inside DCC CHAT!");
1436         return;
1437     }
1438
1439     return "REPLY";
1440 }
1441
1442 1;