1use strict; 2 3use vars qw($VERSION %IRSSI); 4$VERSION = "2003020801"; 5%IRSSI = ( 6 authors => "Stefan 'tommie' Tomanek", 7 contact => "stefan\@pico.ruhr.de", 8 name => "TrustWeb", 9 description => "Illustrates the trust between ops", 10 license => "GPLv2", 11 modules => "Data::Dumper IO::File POSIX", 12 changed => "$VERSION", 13 commands => "trustweb" 14); 15 16 17use Irssi 20020324; 18use Irssi::TextUI; 19use Data::Dumper; 20use IO::File; 21use POSIX; 22use vars qw(%database); 23 24sub draw_box ($$$$) { 25 my ($title, $text, $footer, $colour) = @_; 26 my $box = ''; 27 $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n"; 28 foreach (split(/\n/, $text)) { 29 $box .= '%R|%n '.$_."\n"; 30 } $box .= '%R`--<%n'.$footer.'%R>->%n'; 31 $box =~ s/%.//g unless $colour; 32 return $box; 33} 34 35sub show_help() { 36 my $help = $IRSSI{name}." ".$VERSION." 37/trustweb help 38 Display this help 39/trustweb save/load 40 Load or save the database 41/trustweb show <nick> 42 Display the trust for <nick> 43/trustweb scan 44 Scan all buffers for modechanges 45/trustweb trace <nick1> <nick2> 46 Search the shortest connection between two nicks 47/trustweb merge <nick1> <nick2> 48 Move all trustdata from nick1 to nick2 49"; 50 my $text = ""; 51 foreach (split(/\n/, $help)) { 52 $_ =~ s/^\/(.*)$/%9\/$1%9/; 53 $text .= $_."\n"; 54 } 55 print CLIENTCRAP draw_box($IRSSI{name}, $text, "Help", 1); 56} 57 58 59sub save_db { 60 my $filename = Irssi::settings_get_str('trustweb_db_file'); 61 my $io = new IO::File $filename, "w"; 62 if (defined $io) { 63 my $dumper = Data::Dumper->new([\%database]); 64 $dumper->Purity(1)->Deepcopy(1); 65 $io->print($dumper->Dump); 66 $io->close; 67 } 68 print CLIENTCRAP "%B>>%n Trustweb database saved to ".$filename; 69} 70 71sub load_db { 72 my $filename = Irssi::settings_get_str('trustweb_db_file'); 73 my $io = new IO::File $filename, "r"; 74 if (defined $io) { 75 no strict 'vars'; 76 my $text; 77 $text .= $_ foreach ($io->getlines); 78 my $database = eval "$text"; 79 %database = %$database if ref $database; 80 } 81 print CLIENTCRAP "%B>>%n Trustweb database loaded from ".$filename; 82} 83 84sub scan_buffers { 85 foreach my $channel (Irssi::channels()) { 86 my $win = $channel->window(); 87 my $name = $channel->{name}; 88 my $server = $channel->{server}; 89 my $view = $win->view(); 90 my $line = $view->get_lines(); 91 my $lines = 0; 92 while (defined $line) { 93 my $text = $line->get_text(0); 94 if ($line->{info}{level} == 2048) { 95 if ($text =~ /\[([\+\-].*?)\] by (.*)/) { 96 sig_message_irc_mode($server, $name, $2, undef, $1); 97 } 98 } 99 $line = $line->next; 100 $lines++; 101 } 102 } 103} 104 105sub sig_message_irc_mode ($$$$$) { 106 my ($server, $channel, $nick, $addr, $mode) = @_; 107 return if ($nick =~ /\./); 108 my $state; 109 my @pipe; 110 my %result; 111 my $tag = lc $server->{tag}; 112 my ($modes, $nicks) = split(/ /, $mode, 2); 113 foreach (split(//, $modes)) { 114 if ($_ eq '+' || $_ eq '-') { 115 $state = $_; 116 } else { 117 push @pipe, $state.$_; 118 } 119 } 120 121 foreach (split(/ /, $nicks)) { 122 my $change = shift(@pipe); 123 if ($change eq '+o') { 124 foreach my $active (split /, ?/, $nick) { 125 $database{$tag}{lc $active}{lc $_} = 1; 126 } 127 } elsif ($change eq '-o') { 128 foreach my $active (split /, ?/, $nick) { 129 $database{$tag}{lc $active}{lc $_} = -1; 130 } 131 } 132 } 133} 134 135sub sig_nicklist_changed ($$$) { 136 my ($channel, $nick, $old) = @_; 137 my $server = $channel->{server}; 138 my $new = lc $nick->{nick}; 139 my $tag = lc $server->{tag}; 140 merge_nicks($tag, $old, $new); 141} 142 143sub merge_nicks ($$$) { 144 my ($tag, $old, $new) = @_; 145 $tag = lc $tag; 146 $new = lc $new; 147 $old = lc $old; 148 return if $old eq $new; 149 if (defined $database{$tag}{$old}) { 150 foreach (keys %{ $database{$tag}{$old} }) { 151 $database{$tag}{$new}{$_} = $database{$tag}{$old}{$_}; 152 } 153 delete $database{$tag}{$old} 154 } 155 foreach (keys %{ $database{$tag} }) { 156 if (defined $database{$tag}{$_}{$old}) { 157 $database{$tag}{$_}{$new} = $database{$tag}{$_}{$old}; 158 delete $database{$tag}{$_}{$old}; 159 } 160 } 161} 162 163sub show_trust ($$) { 164 my ($nicks, $tag) = @_; 165 my $text; 166 foreach (@$nicks) { 167 $text .= draw_trust($_, $tag); 168 } 169 print CLIENTCRAP &draw_box('TrustWeb', $text, $tag, 1); 170} 171 172sub draw_trust ($$) { 173 my ($nick, $tag) = @_; 174 my (@opfrom, @opto); 175 my $text; 176 #return unless $database{$nick}; 177 my ($maxfrom, $maxto) = (0, 0); 178 my $distrust = Irssi::settings_get_bool('trustweb_show_distrust'); 179 foreach (sort keys %{ $database{$tag} }) { 180 next unless defined $database{$tag}{$_}{lc $nick}; 181 push @opfrom, [$_,1] if $database{$tag}{$_}{lc $nick} > 0; 182 push @opfrom, [$_,-1] if ($database{$tag}{$_}{lc $nick} < 0 && $distrust); 183 $maxfrom = length($_) if length($_) > $maxfrom; 184 } 185 if (defined $database{$tag}{lc $nick}) { 186 foreach (sort keys %{$database{$tag}{lc $nick}}) { 187 push @opto, [$_,1] if $database{$tag}{lc $nick}{$_} > 0; 188 push @opto, [$_,-1] if ($database{$tag}{lc $nick}{$_} < 0 && $distrust); 189 $maxto = length($_) if length($_) > $maxto; 190 } 191 } 192 my $items = @opfrom > @opto ? @opfrom-1 : @opto-1; 193 my $i = 0; 194 my $center = sprintf("%.0f", $items/2); 195 $center = @opfrom-1 if (@opfrom && not(defined $opfrom[$center])); 196 $center = @opto-1 if (@opto && not(defined $opto[$center])); 197 foreach (0..$items) { 198 my $line; 199 if (defined $opfrom[$_]) { 200 $line .= '<'.$opfrom[$_][0]; 201 $line .= ' ' x ($maxfrom - length($opfrom[$_][0])); 202 $line .= '>'; 203 $line .= '-' if $opfrom[$_][1] > 0; 204 $line .= '%' if $opfrom[$_][1] < 0; 205 $line .= "," if $_ < $center; 206 $line .= "+" if $_ == $center; 207 $line .= "'" if $_ > $center; 208 } else { 209 $line .= ' ' x ($maxfrom+4) if $maxfrom; 210 } 211 if ($_ == $center) { 212 $line .= '-' if @opfrom; 213 $line .= '(%9'.$nick.'%9)'; 214 $line .= '-' if @opto; 215 } else { 216 $line .= ' ' if @opfrom; 217 $line .= ' ' x (length($nick)+2); 218 $line .= ' ' if @opto; 219 } 220 if (defined $opto[$_]) { 221 $line .= "," if $_ < $center; 222 $line .= "+" if $_ == $center; 223 $line .= "'" if $_ > $center; 224 $line .= '-' if $opto[$_][1] > 0; 225 $line .= '%' if $opto[$_][1] < 0; 226 $line .= '<'.$opto[$_][0]; 227 $line .= ' ' x ($maxto - length($opto[$_][0])); 228 $line .= '>'; 229 } else { 230 $line .= ' ' x ($maxto+4) if $maxto; 231 } 232 $text .= $line."\n"; 233 $i++; 234 } 235 return $text; 236} 237 238sub bg_trace ($$$) { 239 my ($tag, $from, $to) = @_; 240 my ($rh, $wh); 241 pipe($rh, $wh); 242 my $pid = fork(); 243 if ($pid > 0) { 244 close $wh; 245 Irssi::pidwait_add($pid); 246 my $pipetag; 247 my @args = ($tag, $from, $to, $rh, \$pipetag); 248 $pipetag = Irssi::input_add(fileno($rh), INPUT_READ, \&pipe_input, \@args); 249 } else { 250 my $result = walk($from, $to, $database{$tag}, {}, [], [], 0); 251 my $dumper = Data::Dumper->new([$result]); 252 $dumper->Purity(1)->Deepcopy(1); 253 print($wh $dumper->Dump()); 254 close $wh; 255 POSIX::_exit(1); 256 } 257} 258 259sub pipe_input ($) { 260 my ($tag, $from, $to, $rh, $pipetag) = @{$_[0]}; 261 my $text; 262 $text .= $_ foreach (<$rh>); 263 close($rh); 264 Irssi::input_remove($$pipetag); 265 no strict 'vars'; 266 my $result = eval "$text"; 267 draw_trace($tag, $from, $to, $result); 268} 269 270sub walk ($$$$$$) { 271 my ($pos, $goal, $data, $visited, $street, $ideal) = @_; 272 my @road = @$street; 273 274 return $ideal if $visited->{$pos}; 275 return $ideal if (@$ideal && not(Irssi::settings_get_bool('trustweb_trace_find_shortest_path'))); 276 return \@road if ($pos eq $goal); 277 return $ideal if (@$ideal && @$street >= @$ideal); 278 return $ideal if (Irssi::settings_get_int('trustweb_trace_max_depth') && @road > Irssi::settings_get_int('trustweb_trace_max_depth')); 279 280 $visited->{$pos} = 1; 281 my $nodistrust = not Irssi::settings_get_bool('trustweb_trace_distrust'); 282 foreach (keys %{ $data->{$pos} }) { 283 next if ($data->{$pos}{$_} < 1 && $nodistrust); 284 push @road, [ $_, 1, $data->{$pos}{$_} ]; 285 $ideal = walk($_, $goal, $data, $visited, \@road, $ideal); 286 pop @road; 287 } 288 foreach (keys %$data) { 289 next unless defined $data->{$_}{$pos}; 290 next if ($data->{$_}{$_} < 1 && $nodistrust); 291 push @road, [ $_, 0, $data->{$_}{$pos} ]; 292 $ideal = walk($_, $goal, $data, $visited, \@road, $ideal); 293 pop @road; 294 } 295 $visited->{$pos} = 0; 296 return $ideal; 297} 298 299 300sub draw_trace ($$$$) { 301 my ($tag, $from, $to, $route) = @_; 302 my $line = "%B<<%n "; 303 if (ref $route && @$route) { 304 $line .= $from; 305 foreach (@$route) { 306 if ($_->[1]) { 307 $line .= ' '; 308 $line .= $_->[2] > 0 ? '=' : '%%'; 309 $line .= '> '; 310 } else { 311 $line .= ' <'; 312 $line .= $_->[2] > 0 ? '=' : '%'; 313 $line .= ' '; 314 } 315 $line .= $_->[0]; 316 } 317 } else { 318 $line .= "No connection between ".$from." and ".$to." could be found."; 319 } 320 print $line; 321} 322 323sub pre_unload { 324 save_db(); 325} 326 327sub cmd_trustweb ($$$) { 328 my ($args, $server, $witem) = @_; 329 my $tag = ref $server ? lc $server->{tag} : lc Irssi::settings_get_str('trustweb_default_ircnet'); 330 my @arg = split(/ +/, $args); 331 if (not(@arg) || $arg[0] eq 'help') { 332 show_help(); 333 } elsif ($arg[0] eq 'scan') { 334 scan_buffers(); 335 print CLIENTCRAP "%R>>%n All buffers scanned for modes"; 336 } elsif ($arg[0] eq 'show' && defined $arg[1]) { 337 shift @arg; 338 show_trust(\@arg, $tag); 339 } elsif ($arg[0] eq 'save') { 340 save_db; 341 } elsif ($arg[0] eq 'load') { 342 load_db; 343 } elsif ($arg[0] eq 'trace' && defined $arg[1] && defined $arg[2]) { 344 bg_trace($tag, lc $arg[1], lc $arg[2]); 345 print CLIENTCRAP "%B>>%n Searching connection between ".$arg[1]." and ".$arg[2]."..."; 346 } elsif ($arg[0] eq 'merge' && defined $arg[1] && defined $arg[2]) { 347 return unless ref $server; 348 merge_nicks($server->{tag}, $arg[1], $arg[2]); 349 print CLIENTCRAP "%B>>%n '".$arg[1]."' has been merged with '".$arg[2]."'"; 350 } 351} 352 353Irssi::settings_add_str($IRSSI{name}, 'trustweb_default_ircnet', ''); 354Irssi::settings_add_str($IRSSI{name}, 'trustweb_db_file', Irssi::get_irssi_dir()."/trustweb_database"); 355Irssi::settings_add_bool($IRSSI{name}, 'trustweb_show_distrust' , 1); 356 357Irssi::settings_add_bool($IRSSI{name}, 'trustweb_trace_distrust' , 1); 358Irssi::settings_add_bool($IRSSI{name}, 'trustweb_trace_find_shortest_path' , 1); 359Irssi::settings_add_int($IRSSI{name}, 'trustweb_trace_max_depth' , 0); 360 361Irssi::signal_add('setup saved', 'save_db'); 362Irssi::signal_add('message irc mode', \&sig_message_irc_mode); 363Irssi::signal_add_first('nicklist changed', \&sig_nicklist_changed); 364 365Irssi::command_bind('trustweb', \&cmd_trustweb); 366 367foreach my $cmd ('save', 'load', 'scan', 'show', 'help', 'trace', 'merge') { 368 Irssi::command_bind('trustweb '.$cmd => 369 sub { cmd_trustweb("$cmd ".$_[0], $_[1], $_[2]); } ); 370} 371 372load_db(); 373 374print CLIENTCRAP '%B>>%n '.$IRSSI{name}.' '.$VERSION.' loaded: /trustweb help for help'; 375