html/ircsec.pl


   1 # by Stefan 'tommie' Tomanek
   2 
   3 use strict;
   4 
   5 use Irssi 20020324;
   6 use Irssi::TextUI;
   7 use Crypt::CBC;
   8 use Digest::MD5 qw(md5 md5_hex md5_base64);;
   9 
  10 use 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 
  25 use vars qw(%channels);
  26 
  27 sub 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 
  39 sub 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 
  57 sub 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 
  76 sub 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 
  99 sub 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 
 116 sub 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 
 134 sub 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 
 153 sub 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 
 188 Irssi::signal_add('message private', sub { decode($_[0], $_[1], $_[2]); });
 189 Irssi::signal_add('message public', sub { decode($_[0], $_[1], $_[4]); });
 190 Irssi::signal_add('message own_private', sub { decode($_[0], $_[1], $_[2]); });
 191 Irssi::signal_add('message own_public', sub { decode($_[0], $_[1], $_[2]); });
 192 
 193 Irssi::signal_add_first('send text', "sig_send_text");
 194 Irssi::signal_add('window changed', sub { Irssi::statusbar_items_redraw('ircsec'); });
 195 Irssi::signal_add('window item changed', sub { Irssi::statusbar_items_redraw('ircsec'); });
 196 
 197 Irssi::statusbar_item_register('ircsec', 0, 'sb_ircsec');
 198 
 199 Irssi::settings_add_str($IRSSI{name}, 'ircsec_default_cipher', 'Blowfish');
 200 
 201 Irssi::command_bind('ircsec', \&cmd_ircsec);
 202 
 203 foreach my $cmd ('unlock', 'secure', 'toggle') {
 204     Irssi::command_bind('ircsec '.$cmd => sub {
 205         cmd_ircsec("$cmd ".$_[0], $_[1], $_[2]); });
 206 }
 207 
 208 print CLIENTCRAP "%B>>%n ".$IRSSI{name}." ".$VERSION." loaded: /ircsec help for help";
 209