1# by Stefan 'tommie' Tomanek 2 3use strict; 4 5use Irssi 20020324; 6use Irssi::TextUI; 7use Crypt::CBC; 8use Digest::MD5 qw(md5 md5_hex md5_base64);; 9 10use vars qw($VERSION %IRSSI); 11$VERSION = '2008051101'; 12%IRSSI = ( 13 authors => 'Stefan \'tommie\' Tomanek', 14 contact => 'stefan@pico.ruhr.de', 15 name => 'IRCSec', 16 description => 'secures your conversation', 17 license => 'GPLv2', 18 changed => $VERSION, 19 modules => 'Crypt::CBC Digest::MD5', 20 sbitems => 'ircsec', 21 commands => "ircsec", 22 23); 24 25use vars qw(%channels); 26 27sub draw_box ($$$$) { 28 my ($title, $text, $footer, $colour) = @_; 29 my $box = ''; 30 $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n"; 31 foreach (split(/\n/, $text)) { 32 $box .= '%R|%n '.$_."\n"; 33 } 34 $box .= '%R`--<%n'.$footer.'%R>->%n'; 35 $box =~ s/%.//g unless $colour; 36 return $box; 37} 38 39sub show_help() { 40 my $help=$IRSSI{name}." ".$VERSION." 41/ircsec secure <key> 42 Encrypt and decrypt conversation in current channel/query with <key> 43/ircsec unlock 44 Disable de/encryption 45/ircsec toggle 46 Temporary dis- or enable security 47"; 48 my $text = ''; 49 foreach (split(/\n/, $help)) { 50 $_ =~ s/^\/(.*)$/%9\/$1%9/; 51 $text .= $_."\n"; 52 } 53 print CLIENTCRAP &draw_box($IRSSI{name}." help", $text, "help", 1) ; 54} 55 56 57sub encrypt ($$$) { 58 my ($text, $key, $algo) = @_; 59 my $cipher; 60 eval { 61 $cipher = Crypt::CBC->new( -key => $key, 62 -cipher => $algo, 63 -iv => '$KJh#(}q', 64 -literal_key => 0, 65 -padding => 'space', 66 -header => 'randomiv' 67 ); 68 69 }; 70 return unless $cipher; 71 my $checksum = md5_base64($text); 72 my $ciphertext = $cipher->encrypt_hex($text." ".$checksum); 73 return $ciphertext; 74} 75 76sub decrypt ($$$) { 77 my ($data, $key, $algo) = @_; 78 my $cipher; 79 eval { 80 $cipher = Crypt::CBC->new( -key => $key, 81 -cipher => $algo, 82 -iv => '$KJh#(}q', 83 -literal_key => 0, 84 -padding => 'space', 85 -header => 'randomiv' 86 ); 87 88 }; 89 return unless $cipher; 90 my $plaintext = $cipher->decrypt_hex($data); 91 my ($text, $checksum) = $plaintext =~ /^(.*) (.*?)$/; 92 if ($checksum eq md5_base64($text)) { 93 return $text; 94 } else { 95 return undef; 96 } 97} 98 99sub sig_send_text ($$$) { 100 my ($line, $server, $witem) = @_; 101 return unless ref $witem; 102 my $tag = $witem->{server}->{tag}; 103 if (defined $channels{$tag}{$witem->{name}} && $channels{$tag}{$witem->{name}}{active}) { 104 my $key = $channels{$tag}{$witem->{name}}{key}; 105 Irssi::signal_stop(); 106 my $cipher = Irssi::settings_get_str('ircsec_default_cipher'); 107 my $crypt = encrypt($line, $key, $cipher); 108# if (defined $crypt) { 109 Irssi::signal_continue("[IRCSec:".$cipher."] ".$crypt, $server, $witem); 110# } else { 111# $witem->print("%R[IRCSec]>%n Unknown cipher method '".$cipher."'", MSGLEVEL_CLIENTCRAP); 112# } 113 } 114} 115 116sub decode ($$$) { 117 my ($server, $text, $target) = @_; 118 return unless ($text =~ /^\[IRCSec(:(.*?))?\] ([\d\w]+)/); 119 my $string = $3; 120 my $cipher = $2; 121 $cipher = Irssi::settings_get_str('ircsec_default_cipher') unless $cipher; 122 my $witem = $server->window_item_find($target); 123 return unless ref $witem; 124 return unless defined $channels{$server->{tag}}{$target}; 125 my $key = $channels{$server->{tag}}{$target}{key}; 126 my $plain = decrypt($string, $key, $cipher); 127 if (defined $plain) { 128 $witem->print("%B[IRCSec:".$cipher."]>%n $plain", MSGLEVEL_CLIENTCRAP); 129 } else { 130 $witem->print("%R[IRCSec]>%n Unknown cipher method '".$cipher."' or wrong key", MSGLEVEL_CLIENTCRAP); 131 } 132} 133 134sub sb_ircsec ($$) { 135 my ($item, $get_size_only) = @_; 136 my $win = !Irssi::active_win() ? undef : Irssi::active_win()->{active}; 137 my $line; 138 if (ref $win && ($win->{type} eq "CHANNEL" || $win->{type} eq "QUERY")){ 139 my $name = $win->{name}; 140 my $tag = $win->{server}->{tag}; 141 if ($channels{$tag}{$name} && $channels{$tag}{$name}{active}) { 142 $line = "%G%Uo-m%U%n"; 143 } elsif ($channels{$tag}{$name}){ 144 $line = "%Ro-m%n"; 145 } 146 } 147 my $format = "{sb ".$line."}"; 148 $item->{min_size} = $item->{max_size} = length($line); 149 $item->default_handler($get_size_only, $format, 0, 1); 150 $item->default_handler($get_size_only, $format, 0, 1); 151} 152 153sub cmd_ircsec ($$$) { 154 my ($args, $server, $witem) = @_; 155 my @arg = split(/ /, $args); 156 if (@arg == 0 || $arg[0] eq 'help') { 157 # do some stuff 158 show_help(); 159 } elsif ($arg[0] eq 'secure') { 160 shift @arg; 161 return unless ref $witem; 162 if (@arg) { 163 my $key = join(' ', @arg); 164 if (length($key) < 8) { 165 $witem->print("%R>>%n Key must be a minimum of 8 characters", MSGLEVEL_CLIENTCRAP); 166 } else { 167 $channels{$server->{tag}}{$witem->{name}}{key} = join(' ', @arg); 168 $channels{$server->{tag}}{$witem->{name}}{active} = 1; 169 $witem->print("%B>>%n %Go-m%n Conversation secured", MSGLEVEL_CLIENTCRAP); 170 } 171 } else { 172 $witem->print("%R>>%n Please specify a key", MSGLEVEL_CLIENTCRAP); 173 } 174 Irssi::statusbar_items_redraw('ircsec'); 175 } elsif ($arg[0] eq 'unlock') { 176 delete $channels{$server->{tag}}{$witem->{name}}; 177 $witem->print("%B>>%n %Ro-m%n Security disabled", MSGLEVEL_CLIENTCRAP); 178 Irssi::statusbar_items_redraw('ircsec'); 179 } elsif ($arg[0] eq 'toggle') { 180 return unless ref $witem; 181 if ($channels{$server->{tag}}{$witem->{name}}) { 182 $channels{$server->{tag}}{$witem->{name}}{active} = not $channels{$server->{tag}}{$witem->{name}}{active}; 183 Irssi::statusbar_items_redraw('ircsec'); 184 } 185 } 186} 187 188Irssi::signal_add('message private', sub { decode($_[0], $_[1], $_[2]); }); 189Irssi::signal_add('message public', sub { decode($_[0], $_[1], $_[4]); }); 190Irssi::signal_add('message own_private', sub { decode($_[0], $_[1], $_[2]); }); 191Irssi::signal_add('message own_public', sub { decode($_[0], $_[1], $_[2]); }); 192 193Irssi::signal_add_first('send text', "sig_send_text"); 194Irssi::signal_add('window changed', sub { Irssi::statusbar_items_redraw('ircsec'); }); 195Irssi::signal_add('window item changed', sub { Irssi::statusbar_items_redraw('ircsec'); }); 196 197Irssi::statusbar_item_register('ircsec', 0, 'sb_ircsec'); 198 199Irssi::settings_add_str($IRSSI{name}, 'ircsec_default_cipher', 'Blowfish'); 200 201Irssi::command_bind('ircsec', \&cmd_ircsec); 202 203foreach my $cmd ('unlock', 'secure', 'toggle') { 204 Irssi::command_bind('ircsec '.$cmd => sub { 205 cmd_ircsec("$cmd ".$_[0], $_[1], $_[2]); }); 206} 207 208print CLIENTCRAP "%B>>%n ".$IRSSI{name}." ".$VERSION." loaded: /ircsec help for help"; 209 210