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