1 # ho_tfind.pl
2 # $Id: ho_tfind.pl,v 1.18 2005/03/27 13:13:34 jvunder Exp $
3 #
4 # Provides extended search functionality for the /TRACE command.
5 #
6 # Part of the Hybrid Oper Script Collection.
7 #
8 # Based on BlackJac's /TFIND and morrow's stat.pl script.
9 #
10 # Known bugs:
11 # * If the output window is closed halfway, the script crashes.
12
13 use strict;
14 use vars qw($VERSION %IRSSI $SCRIPT_NAME);
15
16 use Irssi;
17 use Irssi::Irc;
18 use HOSC::again;
19 use HOSC::again 'HOSC::Base';
20 use HOSC::again 'HOSC::Tools';
21 import HOSC::Tools qw(test_regexps get_equality glob_to_regexp);
22 use Getopt::Long;
23
24 $SCRIPT_NAME = "Trace Find";
25 ($VERSION) = '$Revision: 1.18 $' =~ / (\d+\.\d+) /;
26 %IRSSI = (
27 authors => 'Garion',
28 contact => 'garion@irssi.org',
29 name => 'ho_tfind',
30 description => 'Provides extended search functionality for the /TRACE command.',
31 license => 'Public Domain',
32 url => 'http://www.garion.org/irssi/hosc/',
33 changed => '04 April 2004 12:34:38',
34 );
35
36 my ($stats, $args);
37 my @found_clients; # Storage place for found clients in case of sorting
38 my @cache;
39 my $cache_time;
40 my $cache_tag; # the network tag for which the cache is active.
41
42 # ---------------------------------------------------------------------
43 # /TFIND
44
45 sub cmd_tfind {
46 my ($arguments, $server, $item) = @_;
47
48 $args = parse_arguments($arguments);
49
50 if ($args->{help}) {
51 Irssi::command_runsub ('tfind', 'help', $server, $item);
52 return;
53 }
54
55 if ($args->{error} || length $arguments == 0) {
56 return print_usage();
57 }
58
59 if (!$server) {
60 return ho_print_error("No server in this window.");
61 }
62
63 $stats->{num_clients} = 0;
64 $stats->{num_found} = 0;
65 $stats->{window} = Irssi::active_win();
66 @found_clients = ();
67
68 if ($stats->{busy}) {
69 ho_print_error("Sorry, already performing a TFIND. Please wait.");
70 return;
71 }
72
73 if ($args->{equality} && $args->{equality} !~ /^(n|nu|nr|ur|nur)$/) {
74 ho_print_error("TFIND: Invalid equality " . $args->{equality} . ".");
75 return;
76 }
77
78 for my $property (qw[ rnick ruser rhost rgecos ]) {
79 if (defined $args->{$property} && !test_regexps($args->{$property})) {
80 return ho_print_error("TFIND: Invalid regexp in $property.");
81 }
82 }
83
84 my $search_param_text = get_param_text($args);
85 ho_print("Searching TRACE output with params $search_param_text");
86
87 my $use_cache = Irssi::settings_get_bool('ho_tfind_use_cache');
88 if ($use_cache && !$args->{nocache}) {
89 my $cache_expiry_time =
90 Irssi::settings_get_int('ho_tfind_cache_expiry_time');
91 my $cache_age = time() - $cache_time;
92
93 if (!$use_cache) {
94 ho_print_warning('Cache is not enabled. Use "/set ' .
95 'ho_tfind_use_cache ON" to enable it.');
96 } elsif (@cache == 0) {
97 ho_print_warning('Cache empty. Not using it.');
98 } elsif ($cache_age > $cache_expiry_time) {
99 ho_print_warning('Cache expired. Not using it.');
100 } elsif ($cache_tag ne $server->{tag}) {
101 ho_print_warning('Cache contents are for diffent tag. Replacing cache.');
102 undef @cache;
103 } else {
104 # Phew, can use the cache.
105 ho_print("Using cache: " . scalar @cache . " clients. ".
106 "Age is $cache_age/$cache_expiry_time.");
107 trace_from_cache($server);
108 return;
109 }
110 }
111
112 $server->redirect_event(
113 'command cmd_tfind', 0, '', (split(/\s+/, $arguments) > 2), undef, {
114 "event 203", "redir event_trace_line",
115 "event 204", "redir event_trace_line",
116 "event 205", "redir event_trace_line",
117 "event 709", "redir event_trace_line",
118 "event 206", "redir event_stop",
119 "event 207", "redir event_stop",
120 "event 208", "redir event_stop",
121 "event 209", "redir event_stop",
122 "event 262", "redir event_trace_end",
123 "event 421", "redir event_unknown_command",
124 }
125 );
126
127 $stats->{busy} = 1;
128 $cache_tag = $server->{tag};
129 undef @cache;
130 $cache_time = time;
131 if ($args->{etrace} || $server->{version} =~ /ircd-ratbox/) {
132 $server->send_raw("ETRACE");
133 } else {
134 $server->send_raw("TRACE");
135 }
136 }
137
138 # ---------------------------------------------------------------------
139
140 sub cmd_tfind_help {
141 my ($arguments, $server, $item) = @_;
142 print_help();
143 }
144
145 # ---------------------------------------------------------------------
146
147 sub parse_arguments {
148 my ($arguments) = @_;
149 my $opt;
150
151 # Irssi works with -argument, Getopt::Long expects --argument. Fix.
152 $arguments =~ s/-/--/g;
153
154 # Smart splitting of $arguments: understands "multi word argument".
155 local @ARGV;
156 my @tempargv = split / /, $arguments;
157 my ($in_multi_token, $delimiter, $index) = (0, undef, 0);
158 while (@tempargv) {
159 my $token = shift @tempargv;
160 if ($in_multi_token) {
161 if ($token =~ /$delimiter$/) {
162 # End of multi token argument
163 $token =~ s/$delimiter$//;
164 $ARGV[$index] .= " " . $token;
165 $in_multi_token = 0;
166 $index++;
167 } else {
168 # Continue multi token argument
169 $ARGV[$index] .= " " . $token;
170 }
171 } elsif ($token =~ /^(['"])/ && $token !~ /['"]$/) {
172 # New multi token argument
173 $delimiter = $1;
174 $token =~ s/^['"]//;
175 $ARGV[$index] = $token;
176 $in_multi_token = 1;
177 } else {
178 # Single token argument
179 $ARGV[$index] = $token;
180 $index++;
181 }
182 }
183
184 # Prevent GetOptions frow screwing up the layout in case of errors.
185 # Thanks xmath. :)
186 local $SIG{__WARN__} = sub {
187 my ($msg) = @_;
188 $msg = '/TFIND: ' . $msg;
189 ho_print_error($msg);
190 };
191
192 my $res = GetOptions(
193 'nocache' => \$opt->{nocache},
194 'sort=s' => \$opt->{sort},
195 'nick=s' => \$opt->{nick},
196 'user=s' => \$opt->{user},
197 'host=s' => \$opt->{host},
198 'ip=s' => \$opt->{ip},
199 'gecos=s' => \$opt->{gecos},
200 'rnick=s' => \$opt->{rnick},
201 'ruser=s' => \$opt->{ruser},
202 'rhost=s' => \$opt->{rhost},
203 'rip=s' => \$opt->{rip},
204 'rgecos=s' => \$opt->{rgecos},
205 'equality=s'=> \$opt->{equality},
206 'spoof' => \$opt->{spoof},
207 'nospoof' => \$opt->{nospoof},
208 'oper' => \$opt->{oper},
209 'nooper' => \$opt->{nooper},
210 'etrace' => \$opt->{etrace},
211 'help' => \$opt->{help},
212 '4' => \$opt->{ipv4},
213 '6' => \$opt->{ipv6},
214 'rawcmd=s' => \$opt->{rawcmd},
215 'count' => \$opt->{count},
216 );
217
218 $opt->{error} = 1 unless $res;
219
220 for my $arg (@ARGV) {
221 # If any args left, read them.
222 if (lc $arg eq "help") {
223 $opt->{help} = 1;
224 } else {
225 $opt->{error} = 1;
226 }
227 }
228
229 # Change glob patterns into regexp patterns.
230 for my $var (qw[ nick user host gecos ip ]) {
231 if (defined $opt->{$var}) {
232 # Store the original glob request for displaying later on.
233 $opt->{"glob$var"} = $opt->{$var};
234 # Convert glob to regexp for matching later on.
235 $opt->{$var} = glob_to_regexp($opt->{$var});
236 }
237 }
238
239 # Now restore any -- in the values back to -.
240 for my $key (keys %$opt) {
241 $opt->{$key} =~ s/--/-/g;
242 }
243
244 return $opt;
245 }
246
247 # ---------------------------------------------------------------------
248
249 sub get_param_text {
250 my ($args) = @_;
251 my $text;
252
253 for my $var (qw[ nick user host gecos ip ]) {
254 if (defined $args->{$var} && length $args->{$var}) {
255 $text .= "($var is " . $args->{"glob$var"} . ") ";
256 }
257 if (defined $args->{"r$var"} && length $args->{"r$var"}) {
258 $text .= "($var regexp " . $args->{"r$var"} . ") ";
259 }
260 }
261
262 $text .= "(spoof) " if $args->{spoof};
263 $text .= "(not spoof) " if $args->{nospoof};
264 $text .= "(oper) " if $args->{oper};
265 $text .= "(not oper) " if $args->{nooper};
266 $text .= "(only ipv4) " if $args->{ipv4};
267 $text .= "(only ipv6) " if $args->{ipv6};
268 $text .= "(equality " . $args->{equality} . ") " if $args->{equality};
269
270 $text .= "using ETRACE " if $args->{etrace};
271
272 $text =~ s/\) \(/) and (/g;
273 $text =~ s/ $//;
274 return $text;
275 }
276
277 # ---------------------------------------------------------------------
278 # Catching and processing output of the ircd.
279
280 sub event_trace_line {
281 my ($server, $data, $nick, $address) = @_;
282 my ($ownnick, $line) = $data =~ /^(\S*)\s+(.*)$/;
283
284 my $details = get_trace_line_details($line);
285 return if $details->{crap};
286
287 if (Irssi::settings_get_bool('ho_tfind_use_cache')) {
288 push @cache, $details;
289 }
290 process_line($details, $server);
291 }
292
293 # ---------------------------------------------------------------------
294
295 sub trace_from_cache {
296 my ($server) = @_;
297 for my $details (@cache) {
298 process_line($details, $server);
299 }
300 trace_end();
301 }
302
303 # ---------------------------------------------------------------------
304 # Processes one line of TRACE output, whether retrieved from TRACE output
305 # or from the cache.
306
307 sub process_line {
308 my ($details, $server) = @_;
309
310 $stats->{num_clients}++;
311
312 for my $check (qw[ nick user host gecos ip ]) {
313 # Glob check
314 return if defined $args->{$check} && length $args->{$check} &&
315 $details->{$check} !~ /$args->{$check}/i;
316
317 # Regexp check
318 return if defined $args->{"r$check"} && length $args->{"r$check"} &&
319 $details->{$check} !~ /$args->{"r$check"}/;
320 }
321
322 return if $args->{spoof} && $details->{ip} ne "255.255.255.255";
323 return if $args->{nospoof} && $details->{ip} eq "255.255.255.255";
324 return if $args->{oper} && !$details->{is_oper};
325 return if $args->{nooper} && $details->{is_oper};
326 return if $args->{ipv4} && !$details->{ipv4};
327 return if $args->{ipv6} && !$details->{ipv6};
328
329 if ($args->{equality}) {
330 my $eq = get_equality($details->{nick}, $details->{user},
331 $details->{gecos});
332 return if $eq ne $args->{equality};
333 }
334
335 if (defined $args->{rawcmd}) {
336 execute_raw_command($details, $server);
337 } elsif (defined $args->{sort}) {
338 push @found_clients, $details;
339 } else {
340 if ($args->{'count'}) {
341 # Don't print the client, only list the number of clients found
342 # at the end.
343 } else {
344 print_client($details);
345 }
346 }
347 $stats->{num_found}++;
348 }
349
350 # ---------------------------------------------------------------------
351
352 sub print_client {
353 my ($details) = @_;
354
355 my $format = 'ho_tfind_line';
356 $format = 'ho_tfind_line_v6' if $details->{ipv6};
357
358 $stats->{window}->printformat(MSGLEVEL_CRAP, $format,
359 $details->{nick}, $details->{user}, $details->{host},
360 $details->{gecos}, $details->{ip});
361 }
362
363 # ---------------------------------------------------------------------
364
365 sub execute_raw_command {
366 my ($details, $server) = @_;
367
368 my $cmd = $args->{rawcmd};
369 for (qw[ nick user host gecos ip ]) {
370 $cmd =~ s/%$_%/$details->{$_}/g;
371 }
372 $server->send_raw_now($cmd);
373 }
374
375 # ---------------------------------------------------------------------
376 # Processes a single /TRACE output line and returns a hashref with the
377 # relevant data.
378
379 sub get_trace_line_details {
380 my ($line) = @_;
381
382 my $details;
383
384 # TRACE
385 if ($line =~ /(User|Oper)\s+(\S+)\s+(\S+)\[([^@]+)@(\S+)\]\s+\(([^)]+)\)/) {
386 $details->{is_user} = 1 if $1 eq "User";
387 $details->{is_oper} = 1 if $1 eq "Oper";
388 $details->{class} = $2;
389 $details->{nick} = $3;
390 $details->{user} = $4;
391 $details->{host} = $5;
392 $details->{ip} = $6;
393 $details->{ipv4} = 1 if $details->{ip} !~ /:/;
394 $details->{ipv6} = 1 if $details->{ip} =~ /:/;
395 return $details;
396 }
397
398 # ETRACE
399 if ($line =~ /(User|Oper)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+:(.*)$/) {
400 $details->{is_user} = 1 if $1 eq "User";
401 $details->{is_oper} = 1 if $1 eq "Oper";
402 $details->{class} = $2;
403 $details->{nick} = $3;
404 $details->{user} = $4;
405 $details->{host} = $5;
406 $details->{ip} = $6;
407 $details->{gecos} = $7;
408 $details->{ipv4} = 1 if $details->{ip} !~ /:/;
409 $details->{ipv6} = 1 if $details->{ip} =~ /:/;
410 return $details;
411 }
412
413 $details->{crap} = 1;
414 return $details;
415 }
416
417 # ---------------------------------------------------------------------
418
419 sub signal_stop {
420 my ($server, $data, $nick, $address) = @_;
421 Irssi::signal_stop();
422 }
423
424 # ---------------------------------------------------------------------
425
426 sub event_trace_end {
427 my ($server, $data, $nick, $address) = @_;
428
429 trace_end();
430 }
431
432 # ---------------------------------------------------------------------
433
434 sub trace_end {
435 if (defined $args->{sort}) {
436 my @sorted_clients = sort_clients(@found_clients);
437 print_client($_) for @sorted_clients;
438 }
439
440 ho_print("Found " . $stats->{num_found} . " match" .
441 ($stats->{num_found} == 1 ? "" : "es") .
442 " in " . $stats->{num_clients} . " client" .
443 ($stats->{num_clients} == 1 ? "" : "s") . ".");
444 $stats->{busy} = 0;
445 }
446
447 # ---------------------------------------------------------------------
448
449 sub sort_clients {
450 my @clients = @_;
451
452 my %sort_params = (
453 n => {
454 field => 'nick',
455 order => 'normal',
456 },
457 N => {
458 field => 'nick',
459 order => 'reverse',
460 },
461 u => {
462 field => 'user',
463 order => 'normal',
464 },
465 U => {
466 field => 'user',
467 order => 'reverse',
468 },
469 h => {
470 field => 'host',
471 order => 'normal',
472 },
473 H => {
474 field => 'host',
475 order => 'reverse',
476 },
477 g => {
478 field => 'gecos',
479 order => 'normal',
480 },
481 G => {
482 field => 'gecos',
483 order => 'reverse',
484 },
485 );
486
487 if (exists $sort_params{$args->{sort}}) {
488 return
489 map { $_->[0] }
490 sort {
491 text_compare(
492 $a->[1], $b->[1],
493 $sort_params{$args->{sort}}->{order},
494 Irssi::settings_get_bool('ho_tfind_sort_case_sensitive')
495 )
496 }
497 map { [ $_, $_->{ $sort_params{ $args->{sort} }->{field} } ] }
498 @clients;
499 } elsif ($args->{sort} eq 'i') {
500 return
501 map { $_->[0] }
502 sort { ip_compare($a->[1], $b->[1]) }
503 map { [ $_, $_->{ip} ] }
504 @clients;
505 }
506
507 return @clients;
508 }
509
510 # ---------------------------------------------------------------------
511
512 sub text_compare {
513 my ($first, $second, $reverse, $case_sensitive) = @_;
514
515 if ($reverse eq 'normal') {
516 if ($case_sensitive) {
517 return ($first cmp $second);
518 } else {
519 return ((lc $first) cmp (lc $second));
520 }
521 } else {
522 if ($case_sensitive) {
523 return ((reverse $first) cmp (reverse $second));
524 } else {
525 return ((reverse lc $first) cmp (reverse lc $second));
526 }
527 }
528 }
529
530 # ---------------------------------------------------------------------
531 # This is not an exact ip comparison, but it's Good Enough[tm].
532 # It treats the ip as a number and sorts that.
533
534 sub ip_compare {
535 my ($first, $second) = @_;
536
537 if ($first =~ /\./) {
538 return -1 if $second =~ /:/;
539 return $first <=> $second;
540 } else {
541 return 1 if $second =~ /\./;
542 return $first <=> $second;
543 }
544 }
545
546 # ---------------------------------------------------------------------
547
548 sub event_unknown_command {
549 my ($server, $data, $nick, $address) = @_;
550
551 ho_print_error("This server does not support ETRACE.");
552 $stats->{busy} = 0;
553 }
554
555 # ---------------------------------------------------------------------
556 # Initialisation
557
558 ho_print_init_begin($SCRIPT_NAME);
559
560 Irssi::settings_add_bool('ho', 'ho_tfind_use_cache', 0);
561 Irssi::settings_add_int('ho', 'ho_tfind_cache_expiry_time', 60);
562 Irssi::settings_add_bool('ho', 'ho_tfind_sort_case_sensitive', 0);
563
564 Irssi::command_bind('tfind', 'cmd_tfind');
565 Irssi::command_bind('tfind help', 'cmd_tfind_help');
566
567 Irssi::signal_add({
568 "redir event_trace_line" => \&event_trace_line,
569 "redir event_trace_end" => \&event_trace_end,
570 "redir event_stop" => \&signal_stop,
571 "redir event_unknown_command" => \&event_unknown_command,
572 });
573
574
575 # ok this is IMO realy ugly, if anyone has a suggestion please let me know
576 Irssi::Irc::Server::redirect_register("command cmd_tfind", 0, 0,
577 {
578 "event 203" => 1, # RPL_TRACEUNKNOWN
579 "event 204" => 1, # RPL_TRACEOPERATOR
580 "event 205" => 1, # RPL_TRACEUSER
581 "event 206" => 1, # RPL_TRACESERVER
582 "event 207" => 1, # RPL_TRACESERVICE
583 "event 208" => 1, # RPL_TRACENEWTYPE
584 "event 209" => 1, # RPL_TRACECLASS
585 "event 709" => 1, # ratbox ETRACE output
586 },
587 {
588 "event 219" => 1, # end of stats
589 "event 262" => 1, # end of trace
590 "event 263" => 1, # tryagain
591 "event 401" => 1, # no such server
592 "event 421" => 1, # unknow command (missing ETRACE)
593 },
594 undef,
595 );
596
597 # Register format.
598 # nick, user, host, gecos, ip
599
600 Irssi::theme_register([
601 'ho_tfind_line',
602 '{nick $[-9]0}{comment %g$[!15]4}{chanhost_hilight $[-11]1@$[!38]2}{comment $3}',
603
604 'ho_tfind_line_v6',
605 '{nick $[-9]0}{comment %g$[!24]4}{chanhost_hilight $[-11]1@$[!38]2}{comment $3}',
606 ]);
607
608 ho_print_init_end($SCRIPT_NAME);
609
610 # ---------------------------------------------------------------------
611 # Help.
612
613 sub print_usage {
614 ho_print_help('section', 'Syntax');
615 ho_print_help('syntax', '/TFIND [-]HELP');
616 ho_print_help('syntax', "/TFIND -<switch> [<arg>] -<switch> [<arg>] ..");
617 #ho_print_help('syntax', "/TFIND -<switch> [<arg>] [action [arguments]]\n");
618 #ho_print_help("Use /TFIND -help for help\n");
619 }
620
621 sub print_help {
622 ho_print_help('head', $SCRIPT_NAME);
623
624 print_usage();
625
626 ho_print_help('section', 'Introduction');
627 ho_print_help("Script to search through /TRACE output.\n");
628 ho_print_help("Glob search has * and ? as wildcards.\n");
629 ho_print_help('This script has a cache built in, which is disabled by '.
630 'default. It can be enabled by setting ho_tfind_use_cache to ON. '.
631 'When a TRACE is sent to the server and caching is enabled, the '.
632 'TRACE output is stored internally. If another /TFIND is done '.
633 'shortly after the previous one, the cache data is used instead of '.
634 "sending another TRACE to the server.\n");
635
636 ho_print_help('section', 'Arguments');
637 ho_print_help('argument', '-nocache',
638 'Does not use cache, even if available.');
639
640 my @args = (
641 [qw(nick Glob nickname)],
642 [qw(user Glob username)],
643 [qw(host Glob hostname)],
644 [qw(ip Glob ip)],
645 [qw(gecos Glob gecos)],
646 [qw(rnick Regexp nickname)],
647 [qw(ruser Regexp username)],
648 [qw(rhost Regexp hostname)],
649 [qw(rip Regexp ip)],
650 [qw(rgecos Regexp gecos)],
651 );
652 for my $arg (@args) {
653 ho_print_help('argument', '-' . $arg->[0] . ' <pattern>',
654 $arg->[1] . ' searches for <pattern> in the ' . $arg->[2] . '.');
655 }
656
657 ho_print_help('argument', '-4', 'Searches for ipv4 clients.');
658 ho_print_help('argument', '-6', 'Searches for ipv6 clients.');
659 ho_print_help('argument', '-oper', 'Searches for opers.');
660 ho_print_help('argument', '-nooper', 'Excludes opers.');
661 ho_print_help('argument', '-spoof', 'Searches for spoofs.');
662 ho_print_help('argument', '-nospoof', 'Excludes spoofs.');
663
664 ho_print_help('argument', '-equality <equality>',
665 'Requires this equality. See below.');
666 ho_print_help('argument', '-sort <criterium>',
667 'Sorts by criterium. See below.');
668 ho_print_help('argument', '-rawcmd "<cmd>"',
669 'Executes the given command. See below.');
670
671 ho_print_help('argument', '-etrace', 'Use ETRACE instead of TRACE.');
672
673 ho_print_help('section', 'Settings');
674 ho_print_help('setting', 'ho_tfind_use_cache',
675 'Boolean indicating whether to cache /TRACE output.');
676 ho_print_help('setting', 'ho_tfind_cache_expiry_time',
677 'Time, in seconds, after which the cache becomes invalid.');
678 ho_print_help('setting', 'ho_tfind_sort_case_sensitive',
679 'Whether output sorting is case sensitive.');
680 # ho_print_help('setting', 'ho_tfind_kline_time',
681 # 'Time (in minutes) for -kline option. [not functional yet]');
682 # ho_print_help('setting', 'ho_tfind_kline_reason',
683 # 'Reason for -kline option. [not functional yet]');
684 # ho_print_help('setting', 'ho_tfind_log_file',
685 # 'File to log found clients to. [not functional yet]');
686
687 ho_print_help('section', 'Equality');
688 ho_print_help("Equality is a term which describes the relationship ".
689 "between a client's nick, user and realname (gecos). The following ".
690 "equalities exist:");
691 ho_print_help(" n - all three are different");
692 ho_print_help(" nu - nick equals username, realname is different");
693 ho_print_help(" nr - nick equals realname, username is different");
694 ho_print_help(" ur - username equals realname, nick is different");
695 ho_print_help(" nur - all three are equal\n");
696
697 ho_print_help('section', 'Sorting');
698 ho_print_help("The output of clients can be sorted using the -sort ".
699 "option. The following search criteria are allowed:");
700 ho_print_help(" n - sort by nick");
701 ho_print_help(" N - sort by reversed nick");
702 ho_print_help(" u - sort by username");
703 ho_print_help(" U - sort by reversed username");
704 ho_print_help(" h - sort by host");
705 ho_print_help(" H - sort by reversed host");
706 ho_print_help(" g - sort by gecos");
707 ho_print_help(" G - sort by reversed gecos");
708 ho_print_help(" i - sort by ip");
709 ho_print_help("By default, sorting is done case insensitive. To " .
710 "change this, use the setting ho_tfind_sort_case_sensitive.\n");
711
712 ho_print_help('section', 'Raw command');
713 ho_print_help("Using the -rawcmd option, you can make this script " .
714 "execute a raw IRCD command on all the found clients. Do not " .
715 "forget to put the double quotes around the command.");
716 ho_print_help("To make this feature actually useful, several " .
717 "strings are automatically replaced by the found client's " .
718 "properties before the raw command is sent. These are:");
719 ho_print_help(" %nick% - the client's nick");
720 ho_print_help(" %user% - the client's username");
721 ho_print_help(" %host% - the client's hostname");
722 ho_print_help(" %ip% - the client's ip");
723 ho_print_help(" %gecos% - the client's gecos");
724 ho_print_help("Remember: do not forget the quotes around the command!\n");
725
726 ho_print_help('section', 'Examples');
727 ho_print_help('argument', '/tfind -spoof -nooper -sort H',
728 'Finds all spoofed, non-opered clients, sorted by their '.
729 'reversed hostname.');
730 ho_print_help('argument', '/tfind -rnick ^\[..+\].?[0-9]+$ -equality nu',
731 'Finds all clients with a [abc]-123 kind of nickname, whose ' .
732 'nickname is equal to their username.');
733 ho_print_help('argument', '/tfind -gecos "w3 rul3 j00r 4ss" -rawcmd '.
734 '"PRIVMSG %nick% :.die"', 'Finds all clients with the "we rule" '.
735 'gecos, and sends them a message ".die".');
736 ho_print_help('argument', '/tfind -rnick [A-Z]{4} -rawcmd '.
737 '"DLINE %ip% :drone"', 'Places a D-line on the ip of each client ' .
738 'with at least 4 consecutive uppercase letters in their nick.');
739 }
syntax highlighted by Code2HTML, v. 0.9.1