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