]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/UserDCC.pl
* Add vim formatting comments ( # vim:ts=4:sw=4:expandtab:tw=80 )
[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         &msg($chan, $msg);
361
362         return;
363     }
364
365     # do.
366     if ($message =~ s/^do\s+(\S+)\s+(.*)//) {
367         return unless (&hasFlag('o'));
368         my ($chan,$msg) = (lc $1, $2);
369
370         &DEBUG("chan => '$1', msg => '$msg'.");
371
372         &action($chan, $msg);
373
374         return;
375     }
376
377     # die.
378     if ($message =~ /^die$/) {
379         return unless (&hasFlag('n'));
380
381         &doExit();
382
383         &status("Dying by $who\'s request");
384         exit 0;
385     }
386
387     # global factoid substitution.
388     if ($message =~ m|^\* =~ s([/,#])(.+?)\1(.*?)\1;?\s*$|) {
389         my ($delim,$op,$np) = ($1, $2, $3);
390         return unless (&hasFlag('n'));
391         ### TODO: support flags to do full-on global.
392
393         # incorrect format.
394         if ($np =~ /$delim/) {
395             &performReply("looks like you used the delimiter too many times. You may want to use a different delimiter, like ':' or '#'.");
396             return;
397         }
398
399         ### TODO: fix up $op to support mysql/sqlite/pgsql
400         ### TODO: => add db/sql specific function to fix this.
401         my @list = &searchTable('factoids', 'factoid_key',
402                         'factoid_value', $op);
403
404         if (!scalar @list) {
405             &performReply("Expression didn't match anything.");
406             return;
407         }
408
409         if (scalar @list > 100) {
410             &performReply("regex found more than 100 matches... not doing.");
411             return;
412         }
413
414         &status("gsubst: going to alter ".scalar(@list)." factoids.");
415         &performReply('going to alter '.scalar(@list)." factoids.");
416
417         my $error = 0;
418         foreach (@list) {
419             my $faqtoid = $_;
420
421             next if (&IsLocked($faqtoid) == 1);
422             my $result = &getFactoid($faqtoid);
423             my $was = $result;
424             &DEBUG("was($faqtoid) => '$was'.");
425
426             # global global
427             # we could support global local (once off).
428             if ($result =~ s/\Q$op/$np/gi) {
429                 if (length $result > $param{'maxDataSize'}) {
430                     &performReply("that's too long (or was long)");
431                     return;
432                 }
433                 &setFactInfo($faqtoid, 'factoid_value', $result);
434                 &status("update: '$faqtoid' =is=> '$result'; was '$was'");
435             } else {
436                 &WARN("subst: that's weird... thought we found the string ($op) in '$faqtoid'.");
437                 $error++;
438             }
439         }
440
441         if ($error) {
442             &ERROR("Some warnings/errors?");
443         }
444
445         &performReply("Ok... did s/$op/$np/ for ".
446                                 (scalar(@list) - $error).' factoids');
447
448         return;
449     }
450
451     # jump.
452     if ($message =~ /^jump(\s+(\S+))?$/i) {
453         return unless (&hasFlag('n'));
454
455         if ($2 eq '') {
456             &help('jump');
457             return;
458         }
459
460         my ($server,$port);
461         if ($2 =~ /^(\S+)(:(\d+))?$/) {
462             $server = $1;
463             $port   = $3 || 6667;
464         } else {
465             &msg($who,"invalid format.");
466             return;
467         }
468
469         &status("jumping servers... $server...");
470         $conn->quit("jumping to $server");
471
472         if (&irc($server,$port) == 0) {
473             &ircloop();
474         }
475     }
476
477     # reload.
478     if ($message =~ /^reload$/i) {
479         return unless (&hasFlag('n'));
480
481         &status("USER reload $who");
482         &performStrictReply("reloading...");
483         &reloadAllModules();
484         &performStrictReply("reloaded.");
485
486         return;
487     }
488
489     # reset.
490     if ($message =~ /^reset$/i) {
491         return unless (&hasFlag('n'));
492
493         &msg($who,"resetting...");
494         my @done;
495         foreach ( keys %channels, keys %chanconf ) {
496             my $c = $_;
497             next if (grep /^\Q$c\E$/i, @done);
498
499             &part($_);
500
501             push(@done, $_);
502             sleep 1;
503         }
504         &DEBUG('before clearircvars');
505         &clearIRCVars();
506         &DEBUG('before joinnextchan');
507         &joinNextChan();
508         &DEBUG('after joinnextchan');
509
510         &status("USER reset $who");
511         &msg($who,'reset complete');
512
513         return;
514     }
515
516     # rehash.
517     if ($message =~ /^rehash$/) {
518         return unless (&hasFlag('n'));
519
520         &msg($who,"rehashing...");
521         &restart('REHASH');
522         &status("USER rehash $who");
523         &msg($who,'rehashed');
524
525         return;
526     }
527
528     #####
529     ##### USER//CHAN SPECIFIC CONFIGURATION COMMANDS
530     #####
531
532     if ($message =~ /^chaninfo(\s+(.*))?$/) {
533         my @args = split /[\s\t]+/, $2; # hrm.
534
535         if (scalar @args != 1) {
536             &help('chaninfo');
537             return;
538         }
539
540         if (!exists $chanconf{$args[0]}) {
541             &performStrictReply("no such channel $args[0]");
542             return;
543         }
544
545         &performStrictReply("showing channel conf.");
546         foreach (sort keys %{ $chanconf{$args[0]} }) {
547             &performStrictReply("$chan: $_ => $chanconf{$args[0]}{$_}");
548         }
549         &performStrictReply("End of chaninfo.");
550
551         return;
552     }
553
554     # +chan.
555     if ($message =~ /^(chanset|\+chan)(\s+(.*?))?$/) {
556         my $cmd         = $1;
557         my $args        = $3;
558         my $no_chan     = 0;
559
560         if (!defined $args) {
561             &help($cmd);
562             return;
563         }
564
565         my @chans;
566         while ($args =~ s/^($mask{chan})\s*//) {
567             push(@chans, lc($1));
568         }
569
570         if (!scalar @chans) {
571             push(@chans, '_default');
572             $no_chan    = 1;
573         }
574
575         my($what,$val) = split /[\s\t]+/, $args, 2;
576
577         ### TODO: "cannot set values without +m".
578         return unless (&hasFlag('n'));
579
580         # READ ONLY.
581         if (defined $what and $what !~ /^[-+]/ and !defined $val and $no_chan) {
582             &performStrictReply("Showing $what values on all channels...");
583
584             my %vals;
585             foreach (keys %chanconf) {
586                 my $val;
587                 if (defined $chanconf{$_}{$what}) {
588                     $val = $chanconf{$_}{$what};
589                 } else {
590                     $val = "NOT-SET";
591                 }
592                 $vals{$val}{$_} = 1;
593             }
594
595             foreach (keys %vals) {
596                 &performStrictReply("  $what = $_(" . scalar(keys %{$vals{$_}}) . "): ".join(' ', sort keys %{ $vals{$_} } ) );
597             }
598
599             &performStrictReply("End of list.");
600
601             return;
602         }
603
604         ### TODO: move to UserDCC again.
605         if ($cmd eq 'chanset' and !defined $what) {
606             &DEBUG("showing channel conf.");
607
608             foreach $chan (@chans) {
609                 if ($chan eq '_default') {
610                     &performStrictReply('Default channel settings');
611                 } else {
612                     &performStrictReply("chan: $chan (see _default also)");
613                 }
614                 my @items;
615                 my $str = '';
616                 foreach (sort keys %{ $chanconf{$chan} }) {
617                     my $newstr = join(', ', @items);
618                     ### TODO: make length use channel line limit?
619                     if (length $newstr > 370) {
620                         &performStrictReply(" $str");
621                         @items = ();
622                     }
623                     $str = $newstr;
624                     push(@items, "$_ => $chanconf{$chan}{$_}");
625                 }
626                 if (@items) {
627                     my $str = join(', ', @items);
628                     &performStrictReply(" $str");
629                 }
630             }
631             return;
632         }
633
634         $cache{confvars}{$what} = $val;
635         &rehashConfVars();
636
637         foreach (@chans) {
638             &chanSet($cmd, $_, $what, $val);
639         }
640
641         return;
642     }
643
644     if ($message =~ /^(chanunset|\-chan)(\s+(.*))?$/) {
645         return unless (&hasFlag('n'));
646         my $args        = $3;
647         my $no_chan     = 0;
648
649         if (!defined $args) {
650             &help('chanunset');
651             return;
652         }
653
654         my ($chan);
655         my $delete      = 0;
656         if ($args =~ s/^(\-)?($mask{chan})\s*//) {
657             $chan       = $2;
658             $delete     = ($1) ? 1 : 0;
659         } else {
660             &VERB("no chan arg; setting to default.",2);
661             $chan       = '_default';
662             $no_chan    = 1;
663         }
664
665         if (!exists $chanconf{$chan}) {
666             &performStrictReply("no such channel $chan");
667             return;
668         }
669
670         if ($args ne '') {
671
672             if (!&getChanConf($args,$chan)) {
673                 &performStrictReply("$args does not exist for $chan");
674                 return;
675             }
676
677             my @chans = &ChanConfList($args);
678             &DEBUG("scalar chans => ".scalar(@chans) );
679             if (scalar @chans == 1 and $chans[0] eq '_default' and !$no_chan) {
680                 &performStrictReply("ok, $args was set only for _default; unsetting for _defaul but setting for other chans.");
681
682                 my $val = $chanconf{$_}{_default};
683                 foreach (keys %chanconf) {
684                     $chanconf{$_}{$args} = $val;
685                 }
686                 delete $chanconf{_default}{$args};
687                 $cache{confvars}{$args} = 0;
688                 &rehashConfVars();
689
690                 return;
691             }
692
693             if ($no_chan and !exists($chanconf{_default}{$args})) {
694                 &performStrictReply("ok, $args for _default does not exist, removing from all chans.");
695
696                 foreach (keys %chanconf) {
697                     next unless (exists $chanconf{$_}{$args});
698                     &DEBUG("delete chanconf{$_}{$args};");
699                     delete $chanconf{$_}{$args};
700                 }
701                 $cache{confvars}{$args} = 0;
702                 &rehashConfVars();
703
704                 return;
705             }
706
707             &performStrictReply("Unsetting channel ($chan) option $args. (was $chanconf{$chan}{$args})");
708             delete $chanconf{$chan}{$args};
709
710             return;
711         }
712
713         if ($delete) {
714             &performStrictReply("Deleting channel $chan for sure!");
715             $utime_chanfile = time();
716             $ucount_chanfile++;
717
718             &part($chan);
719             &performStrictReply("Leaving $chan...");
720
721             delete $chanconf{$chan};
722         } else {
723             &performStrictReply("Prefix channel with '-' to delete for sure.");
724         }
725
726         return;
727     }
728
729     if ($message =~ /^newpass(\s+(.*))?$/) {
730         my(@args) = split /[\s\t]+/, $2 || '';
731
732         if (scalar @args != 1) {
733             &help('newpass');
734             return;
735         }
736
737         my $u = &getUser($who);
738         my $crypt = &mkcrypt($args[0]);
739
740         &performStrictReply("Set your passwd to '$crypt'");
741         $users{$u}{PASS} = $crypt;
742
743         $utime_userfile = time();
744         $ucount_userfile++;
745
746         return;
747     }
748
749     if ($message =~ /^chpass(\s+(.*))?$/) {
750         my(@args) = split /[\s\t]+/, $2 || '';
751
752         if (!scalar @args) {
753             &help('chpass');
754             return;
755         }
756
757         if (!&IsUser($args[0])) {
758             &performStrictReply("user $args[0] is not valid.");
759             return;
760         }
761
762         my $u = &getUser($args[0]);
763         if (!defined $u) {
764             &performStrictReply("Internal error, u = NULL.");
765             return;
766         }
767
768         if (scalar @args == 1) {
769             # del pass.
770             if (!&IsFlag('n') and $who !~ /^\Q$verifyUser\E$/i) {
771                 &performStrictReply("cannot remove passwd of others.");
772                 return;
773             }
774
775             if (!exists $users{$u}{PASS}) {
776                 &performStrictReply("$u does not have pass set anyway.");
777                 return;
778             }
779
780             &performStrictReply("Deleted pass from $u.");
781
782             $utime_userfile = time();
783             $ucount_userfile++;
784
785             delete $users{$u}{PASS};
786
787             return;
788         }
789
790         my $crypt       = &mkcrypt($args[1]);
791         &performStrictReply("Set $u's passwd to '$crypt'");
792         $users{$u}{PASS} = $crypt;
793
794         $utime_userfile = time();
795         $ucount_userfile++;
796
797         return;
798     }
799
800     if ($message =~ /^chattr(\s+(.*))?$/) {
801         my(@args) = split /[\s\t]+/, $2 || '';
802
803         if (!scalar @args) {
804             &help('chattr');
805             return;
806         }
807
808         my $chflag;
809         my $user;
810         if ($args[0] =~ /^$mask{nick}$/i) {
811             # <nick>
812             $user       = &getUser($args[0]);
813             $chflag     = $args[1];
814         } else {
815             # <flags>
816             $user       = &getUser($who);
817             &DEBUG("user $who... nope.") unless (defined $user);
818             $user       = &getUser($verifyUser);
819             $chflag     = $args[0];
820         }
821
822         if (!defined $user) {
823             &performStrictReply("user does not exist.");
824             return;
825         }
826
827         my $flags = $users{$user}{FLAGS};
828         if (!defined $chflag) {
829             &performStrictReply("Flags for $user: $flags");
830             return;
831         }
832
833         &DEBUG("who => $who");
834         &DEBUG("verifyUser => $verifyUser");
835         if (!&IsFlag('n') and $who !~ /^\Q$verifyUser\E$/i) {
836             &performStrictReply("cannto change attributes of others.");
837             return 'REPLY';
838         }
839
840         my $state;
841         my $change      = 0;
842         foreach (split //, $chflag) {
843             if ($_ eq "+") { $state = 1; next; }
844             if ($_ eq "-") { $state = 0; next; }
845
846             if (!defined $state) {
847                 &performStrictReply("no initial + or - was found in attr.");
848                 return;
849             }
850
851             if ($state) {
852                 next if ($flags =~ /\Q$_\E/);
853                 $flags .= $_;
854             } else {
855                 if (&IsParam('owner')
856                         and $param{owner} =~ /^\Q$user\E$/i
857                         and $flags =~ /[nmo]/
858                 ) {
859                     &performStrictReply("not removing flag $_ for $user.");
860                     next;
861                 }
862                 next unless ($flags =~ s/\Q$_\E//);
863             }
864
865             $change++;
866         }
867
868         if ($change) {
869             $utime_userfile = time();
870             $ucount_userfile++;
871             #$flags.*FLAGS sort
872             $flags = join('', sort split('', $flags));
873             &performStrictReply("Current flags: $flags");
874             $users{$user}{FLAGS} = $flags;
875         } else {
876             &performStrictReply("No flags changed: $flags");
877         }
878
879         return;
880     }
881
882     if ($message =~ /^chnick(\s+(.*))?$/) {
883         my(@args) = split /[\s\t]+/, $2 || '';
884
885         if ($who eq '_default') {
886             &WARN("$who or verifyuser tried to run chnick.");
887             return 'REPLY';
888         }
889
890         if (!scalar @args or scalar @args > 2) {
891             &help('chnick');
892             return;
893         }
894
895         if (scalar @args == 1) {        # 1
896             $user       = &getUser($who);
897             &DEBUG("nope, not $who.") unless (defined $user);
898             $user       ||= &getUser($verifyUser);
899             $chnick     = $args[0];
900         } else {                        # 2
901             $user       = &getUser($args[0]);
902             $chnick     = $args[1];
903         }
904
905         if (!defined $user) {
906             &performStrictReply("user $who or $args[0] does not exist.");
907             return;
908         }
909
910         if ($user =~ /^\Q$chnick\E$/i) {
911             &performStrictReply("user == chnick. why should I do that?");
912             return;
913         }
914
915         if (&getUser($chnick)) {
916             &performStrictReply("user $chnick is already used!");
917             return;
918         }
919
920         if (!&IsFlag('n') and $who !~ /^\Q$verifyUser\E$/i) {
921             &performStrictReply("cannto change nick of others.");
922             return 'REPLY' if ($who eq '_default');
923             return;
924         }
925
926         foreach (keys %{ $users{$user} }) {
927             $users{$chnick}{$_} = $users{$user}{$_};
928             delete $users{$user}{$_};
929         }
930         undef $users{$user};    # ???
931
932         $utime_userfile = time();
933         $ucount_userfile++;
934
935         &performStrictReply("Changed '$user' to '$chnick' successfully.");
936
937         return;
938     }
939
940     if ($message =~ /^([-+])host(\s+(.*))?$/) {
941         my $cmd         = $1.'host';
942         my(@args)       = split /[\s\t]+/, $3 || '';
943         my $state       = ($1 eq "+") ? 1 : 0;
944
945         if (!scalar @args) {
946             &help($cmd);
947             return;
948         }
949
950         if ($who eq '_default') {
951             &WARN("$who or verifyuser tried to run $cmd.");
952             return 'REPLY';
953         }
954
955         my ($user,$mask);
956         if ($args[0] =~ /^$mask{nick}$/i) {     # <nick>
957             return unless (&hasFlag('n'));
958             $user       = &getUser($args[0]);
959             $mask       = $args[1];
960         } else {                                # <mask>
961             # FIXME: who or verifyUser. (don't remember why)
962             $user       = &getUser($who);
963             $mask       = $args[0];
964         }
965
966         if (!defined $user) {
967             &performStrictReply("user $user does not exist.");
968             return;
969         }
970
971         if (!defined $mask) {
972             &performStrictReply("Hostmasks for $user: " . join(' ', keys %{$users{$user}{HOSTS}}));
973             return;
974         }
975
976         if (!&IsFlag('n') and $who !~ /^\Q$verifyUser\E$/i) {
977             &performStrictReply("cannto change masks of others.");
978             return;
979         }
980
981         my $count = scalar keys %{ $users{$user}{HOSTS} };
982
983         if ($state) {                           # add.
984             if ($mask !~ /^$mask{nuh}$/) {
985                 &performStrictReply("error: mask ($mask) is not a real hostmask.");
986                 return;
987             }
988
989             if (exists $users{$user}{HOSTS}{$mask}) {
990                 &performStrictReply("mask $mask already exists.");
991                 return;
992             }
993
994             ### TODO: override support.
995             $users{$user}{HOSTS}{$mask} = 1;
996
997             &performStrictReply("Added $mask to list of masks.");
998
999         } else {                                # delete.
1000
1001             if (!exists $users{$user}{HOSTS}{$mask}) {
1002                 &performStrictReply("mask $mask does not exist.");
1003                 return;
1004             }
1005
1006             ### TODO: wildcard support. ?
1007             delete $users{$user}{HOSTS}{$mask};
1008
1009             if (scalar keys %{ $users{$user}{HOSTS} } != $count) {
1010                 &performStrictReply("Removed $mask from list of masks.");
1011             } else {
1012                 &performStrictReply("error: could not find $mask in list of masks.");
1013                 return;
1014             }
1015         }
1016
1017         $utime_userfile = time();
1018         $ucount_userfile++;
1019
1020         return;
1021     }
1022
1023     if ($message =~ /^([-+])ban(\s+(.*))?$/) {
1024         my $cmd         = $1.'ban';
1025         my $flatarg     = $3;
1026         my(@args)       = split /[\s\t]+/, $3 || '';
1027         my $state       = ($1 eq "+") ? 1 : 0;
1028
1029         if (!scalar @args) {
1030             &help($cmd);
1031             return;
1032         }
1033
1034         my($mask,$chan,$time,$reason);
1035
1036         if ($flatarg =~ s/^($mask{nuh})\s*//) {
1037             $mask = $1;
1038         } else {
1039             &DEBUG("arg does not contain nuh mask?");
1040         }
1041
1042         if ($flatarg =~ s/^($mask{chan})\s*//) {
1043             $chan = $1;
1044         } else {
1045             $chan = '*';        # _default instead?
1046         }
1047
1048         if ($state == 0) {              # delete.
1049             my @c = &banDel($mask);
1050
1051             foreach (@c) {
1052                 &unban($mask, $_);
1053             }
1054
1055             if (@c) {
1056                 &performStrictReply("Removed $mask from chans: @c");
1057             } else {
1058                 &performStrictReply("$mask was not found in ban list.");
1059             }
1060
1061             return;
1062         }
1063
1064         ###
1065         # add ban.
1066         ###
1067
1068         # time.
1069         if ($flatarg =~ s/^(\d+)\s*//) {
1070             $time = $1;
1071             &DEBUG("time = $time.");
1072             if ($time < 0) {
1073                 &performStrictReply("error: time cannot be negatime?");
1074                 return;
1075             }
1076         } else {
1077             $time = 0;
1078         }
1079
1080         if ($flatarg =~ s/^(.*)$//) {   # need length?
1081             $reason     = $1;
1082         }
1083
1084         if (!&IsFlag('n') and $who !~ /^\Q$verifyUser\E$/i) {
1085             &performStrictReply("cannto change masks of others.");
1086             return;
1087         }
1088
1089         if ($mask !~ /^$mask{nuh}$/) {
1090             &performStrictReply("error: mask ($mask) is not a real hostmask.");
1091             return;
1092         }
1093
1094         if ( &banAdd($mask,$chan,$time,$reason) == 2) {
1095             &performStrictReply("ban already exists; overwriting.");
1096         }
1097         &performStrictReply("Added $mask for $chan (time => $time, reason => $reason)");
1098
1099         return;
1100     }
1101
1102     if ($message =~ /^whois(\s+(.*))?$/) {
1103         my $arg = $2;
1104
1105         if (!defined $arg) {
1106             &help('whois');
1107             return;
1108         }
1109
1110         my $user = &getUser($arg);
1111         if (!defined $user) {
1112             &performStrictReply("whois: user $user does not exist.");
1113             return;
1114         }
1115
1116         ### TODO: better (eggdrop-like) output.
1117         &performStrictReply("user: $user");
1118         foreach (keys %{ $users{$user} }) {
1119             my $ref = ref $users{$user}{$_};
1120
1121             if ($ref eq 'HASH') {
1122                 my $type = $_;
1123                 ### DOES NOT WORK???
1124                 foreach (keys %{ $users{$user}{$type} }) {
1125                     &performStrictReply("    $type => $_");
1126                 }
1127                 next;
1128             }
1129
1130             &performStrictReply("    $_ => $users{$user}{$_}");
1131         }
1132         &performStrictReply("End of USER whois.");
1133
1134         return;
1135     }
1136
1137     if ($message =~ /^bans(\s+(.*))?$/) {
1138         my $arg = $2;
1139
1140         if (defined $arg) {
1141             if ($arg ne '_default' and !&validChan($arg) ) {
1142                 &performStrictReply("error: chan $chan is invalid.");
1143                 return;
1144             }
1145         }
1146
1147         if (!scalar keys %bans) {
1148             &performStrictReply("Ban list is empty.");
1149             return;
1150         }
1151
1152         my $c;
1153         &performStrictReply("     mask: expire, time-added, count, who-by, reason");
1154         foreach $c (keys %bans) {
1155             next unless (!defined $arg or $arg =~ /^\Q$c\E$/i);
1156             &performStrictReply("  $c:");
1157
1158             foreach (keys %{ $bans{$c} }) {
1159                 my $val = $bans{$c}{$_};
1160
1161                 if (ref $val eq 'ARRAY') {
1162                     my @array = @{ $val };
1163                     &performStrictReply("    $_: @array");
1164                 } else {
1165                     &DEBUG("unknown ban: $val");
1166                 }
1167             }
1168         }
1169         &performStrictReply("END of bans.");
1170
1171         return;
1172     }
1173
1174     if ($message =~ /^banlist(\s+(.*))?$/) {
1175         my $arg = $2;
1176
1177         if (defined $arg and $arg !~ /^$mask{chan}$/) {
1178             &performStrictReply("error: chan $chan is invalid.");
1179             return;
1180         }
1181
1182         &DEBUG("bans for global or arg => $arg.");
1183         foreach (keys %bans) {                  #CHANGE!!!
1184             &DEBUG("  $_ => $bans{$_}.");
1185         }
1186
1187         &DEBUG("End of bans.");
1188         &performStrictReply("END of bans.");
1189
1190         return;
1191     }
1192
1193     if ($message =~ /^save$/) {
1194         return unless (&hasFlag('o'));
1195
1196         &writeUserFile();
1197         &writeChanFile();
1198         &performStrictReply('saved user and chan files');
1199
1200         return;
1201     }
1202
1203     ### ALIASES.
1204     $message =~ s/^addignore/+ignore/;
1205     $message =~ s/^(del|un)ignore/-ignore/;
1206
1207     # ignore.
1208     if ($message =~ /^(\+|\-)ignore(\s+(.*))?$/i) {
1209         return unless (&hasFlag('o'));
1210         my $state       = ($1 eq "+") ? 1 : 0;
1211         my $str         = $1.'ignore';
1212         my $args        = $3;
1213
1214         if (!$args) {
1215             &help($str);
1216             return;
1217         }
1218
1219         my($mask,$chan,$time,$comment);
1220
1221         # mask.
1222         if ($args =~ s/^($mask{nuh})\s*//) {
1223             $mask = $1;
1224         } else {
1225             &ERROR("no NUH mask?");
1226             return;
1227         }
1228
1229         if (!$state) {                  # delignore.
1230             if ( &ignoreDel($mask) ) {
1231                 &performStrictReply("ok, deleted ignores for $mask.");
1232             } else {
1233                 &performStrictReply("could not find $mask in ignore list.");
1234             }
1235             return;
1236         }
1237
1238         ###
1239         # addignore.
1240         ###
1241
1242         # chan.
1243         if ($args =~ s/^($mask{chan}|\*)\s*//) {
1244             $chan = $1;
1245         } else {
1246             $chan = '*';
1247         }
1248
1249         # time.
1250         if ($args =~ s/^(\d+)\s*//) {
1251             $time = $1; # time is in minutes
1252         } else {
1253             $time = 0;
1254         }
1255
1256         # time.
1257         if ($args) {
1258             $comment = $args;
1259         } else {
1260             $comment = "added by $who";
1261         }
1262
1263         if ( &ignoreAdd($mask, $chan, $time, $comment) > 1) {
1264             &performStrictReply("FIXME: $mask already in ignore list; written over anyway.");
1265         } else {
1266             &performStrictReply("added $mask to ignore list.");
1267         }
1268
1269         return;
1270     }
1271
1272     if ($message =~ /^ignore(\s+(.*))?$/) {
1273         my $arg = $2;
1274
1275         if (defined $arg) {
1276             if ($arg !~ /^$mask{chan}$/) {
1277                 &performStrictReply("error: chan $chan is invalid.");
1278                 return;
1279             }
1280
1281             if (!&validChan($arg)) {
1282                 &performStrictReply("error: chan $arg is invalid.");
1283                 return;
1284             }
1285
1286             &performStrictReply("Showing bans for $arg only.");
1287         }
1288
1289         if (!scalar keys %ignore) {
1290             &performStrictReply("Ignore list is empty.");
1291             return;
1292         }
1293
1294         ### TODO: proper (eggdrop-like) formatting.
1295         my $c;
1296         &performStrictReply("    mask: expire, time-added, who, comment");
1297         foreach $c (keys %ignore) {
1298             next unless (!defined $arg or $arg =~ /^\Q$c\E$/i);
1299             &performStrictReply("  $c:");
1300
1301             foreach (keys %{ $ignore{$c} }) {
1302                 my $ref = ref $ignore{$c}{$_};
1303                 if ($ref eq 'ARRAY') {
1304                     my @array = @{ $ignore{$c}{$_} };
1305                     &performStrictReply("      $_: @array");
1306                 } else {
1307                     &DEBUG("unknown ignore line?");
1308                 }
1309             }
1310         }
1311         &performStrictReply("END of ignore.");
1312
1313         return;
1314     }
1315
1316     # adduser/deluser.
1317     if ($message =~ /^(add|del)user(\s+(.*))?$/i) {
1318         my $str         = $1;
1319         my $strstr      = $1.'user';
1320         my @args        = split /\s+/, $3 || '';
1321         my $args        = $3;
1322         my $state       = ($str =~ /^(add)$/) ? 1 : 0;
1323
1324         if (!scalar @args) {
1325             &help($strstr);
1326             return;
1327         }
1328
1329         if ($str eq 'add') {
1330             if (scalar @args != 2) {
1331                 &performStrictReply('adduser requires hostmask argument.');
1332                 return;
1333             }
1334         } elsif (scalar @args != 1) {
1335             &performStrictReply('too many arguments.');
1336             return;
1337         }
1338
1339         if ($state) {
1340             # adduser.
1341             if (scalar @args == 1) {
1342                 $args[1]        = &getHostMask($args[0]);
1343                 &performStrictReply("Attemping to guess $args[0]'s hostmask...");
1344
1345                 # crude hack... crappy Net::IRC
1346                 $conn->schedule(5, sub {
1347                     # hopefully this is right.
1348                     my $nick = (keys %{ $cache{nuhInfo} })[0];
1349                     if (!defined $nick) {
1350                         &performStrictReply("couldn't get nuhinfo... adding user without a hostmask.");
1351                         &userAdd($nick);
1352                         return;
1353                     }
1354                     my $mask = &makeHostMask( $cache{nuhInfo}{$nick}{NUH} );
1355
1356                     if ( &userAdd($nick, $mask) ) {
1357                         # success.
1358                         &performStrictReply("Added $nick with flags $users{$nick}{FLAGS}");
1359                         my @hosts = keys %{ $users{$nick}{HOSTS} };
1360                         &performStrictReply("hosts: @hosts");
1361                     }
1362                 });
1363                 return;
1364             }
1365
1366             &DEBUG("args => @args");
1367             if ( &userAdd(@args) ) {    # success.
1368                 &performStrictReply("Added $args[0] with flags $users{$args[0]}{FLAGS}");
1369                 my @hosts = keys %{ $users{$args[0]}{HOSTS} };
1370                 &performStrictReply("hosts: @hosts");
1371
1372             } else {                    # failure.
1373                 &performStrictReply("User $args[0] already exists");
1374             }
1375
1376         } else {                        # deluser.
1377
1378             if ( &userDel($args[0]) ) { # success.
1379                 &performStrictReply("Deleted $args[0] successfully.");
1380
1381             } else {                    # failure.
1382                 &performStrictReply("User $args[0] does not exist.");
1383             }
1384
1385         }
1386         return;
1387     }
1388
1389     if ($message =~ /^sched$/) {
1390         my @list;
1391         my @run;
1392
1393         my %time;
1394         foreach (keys %sched) {
1395             next unless (exists $sched{$_}{TIME});
1396             $time{ $sched{$_}{TIME}-time() }{$_} = 1;
1397             push(@list,$_);
1398
1399             next unless (exists $sched{$_}{RUNNING});
1400             push(@run,$_);
1401         }
1402
1403         my @time;
1404         foreach (sort { $a <=> $b } keys %time) {
1405             my $str = join(', ', sort keys %{ $time{$_} });
1406             &DEBUG("time => $_, str => $str");
1407             push(@time, "$str (".&Time2String($_).")");
1408         }
1409
1410         &performStrictReply( &formListReply(0, "Schedulers: ", @time ) );
1411         &performStrictReply( &formListReply(0, "Scheds to run: ", sort @list ) );
1412         &performStrictReply( &formListReply(0, "Scheds running(should not happen?) ", sort @run ) );
1413
1414         return;
1415     }
1416
1417     # quite a cool hack: reply in DCC CHAT.
1418     $msgType = 'chat' if (exists $dcc{'CHAT'}{$who});
1419
1420     my $done = 0;
1421     $done++ if &parseCmdHook($message);
1422     $done++ unless (&Modules());
1423
1424     if ($done) {
1425         &DEBUG("running non DCC CHAT command inside DCC CHAT!");
1426         return;
1427     }
1428
1429     return 'REPLY';
1430 }
1431
1432 1;
1433
1434 # vim:ts=4:sw=4:expandtab:tw=80